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
* 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.
* 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.
@ -35,9 +40,10 @@ Expected: July 2020
* `--with-restconf=evhtp Integrate restconf with libevhtp server`
* `--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:
* event_reg_fd() -> clixon_event_reg_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 `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
12 May 2020

View file

@ -1066,7 +1066,7 @@ from_client_get(clicon_handle h,
(ret = xml_yang_validate_add(h, xret, &xerr)) < 0)
goto done;
if (ret == 0){
if (debug)
if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, xret, "VALIDATE_STATE");
if (clixon_netconf_internal_error(xerr,
". 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 */
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>");
ok:
retval = 0;

View file

@ -453,7 +453,7 @@ from_validate_common(clicon_handle h,
&td->td_tcvec, /* changed: wanted values */
&td->td_clen) < 0)
goto done;
if (debug>1)
if (clicon_debug_get()>1)
transaction_print(stderr, td);
/* Mark as changed in tree */
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);
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.
* 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 */
size_t cligen_buflen;
size_t cligen_bufthreshold;
int dbg;
/* In the startup, logs to stderr & syslog and debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
@ -472,6 +473,7 @@ main(int argc,
once = 0;
zap = 0;
extraxml_file = NULL;
dbg = 0;
/*
* Command-line options for help, debug, and config-file
@ -489,7 +491,7 @@ main(int argc,
help = 1;
break;
case 'D' : /* debug */
if (sscanf(optarg, "%d", &debug) != 1)
if (sscanf(optarg, "%d", &dbg) != 1)
usage(h, argv[0]);
break;
case 'f': /* config file */
@ -513,8 +515,8 @@ main(int argc,
* XXX: if started in a start-daemon script, there will be irritating
* double syslogs until fork below.
*/
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, logdst);
clicon_debug_init(debug, NULL);
clicon_log_init(__PROGRAM__, dbg?LOG_DEBUG:LOG_INFO, logdst);
clicon_debug_init(dbg, NULL);
/* Find and read configfile */
if (clicon_options_main(h) < 0){
@ -622,7 +624,7 @@ main(int argc,
/* Access the remaining argv/argc options (after --) w clicon-argv_get() */
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 */
if (help)
@ -880,7 +882,7 @@ main(int argc,
demonized errors OK. Before this stage, errors are logged on stderr
also */
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);
if (daemon(0, 0) < 0){
fprintf(stderr, "config: daemon");
@ -911,8 +913,8 @@ main(int argc,
goto done;
if (clicon_socket_set(h, ss) < 0)
goto done;
if (debug)
clicon_option_dump(h, debug);
if (dbg)
clicon_option_dump(h, dbg);
/* Depending on configure setting, privileges may be dropped here after
* initializations */
if (check_drop_priv(h, gid) < 0)

View file

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

View file

@ -2,7 +2,8 @@
*
***** 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.
@ -62,9 +63,9 @@
#include "cli_plugin.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 */
#define GENERATE_EXPAND_XMLDB "expand_dbvar"
@ -340,8 +341,8 @@ yang2cli_var_pattern(clicon_handle h,
}
/* Forward */
static int yang2cli_stmt(clicon_handle h, yang_stmt *ys,
enum genmodel_type gt, int level, cbuf *cb);
static int yang2cli_stmt(clicon_handle h, yang_stmt *ys, enum genmodel_type gt,
int level, int state, cbuf *cb);
static int yang2cli_var_union(clicon_handle h, yang_stmt *ys, char *origtype,
yang_stmt *ytype, char *helptext, cbuf *cb);
@ -657,7 +658,7 @@ yang2cli_leaf(clicon_handle h,
*s = '\0';
}
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));
if (helptext)
cprintf(cb, "(\"%s\")", helptext);
@ -686,6 +687,7 @@ yang2cli_leaf(clicon_handle h,
* @param[in] ys Yang statement
* @param[in] gt CLI Generate style
* @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
@ -693,6 +695,7 @@ yang2cli_container(clicon_handle h,
yang_stmt *ys,
enum genmodel_type gt,
int level,
int state,
cbuf *cb)
{
yang_stmt *yc;
@ -700,7 +703,13 @@ yang2cli_container(clicon_handle h,
int retval = -1;
char *helptext = NULL;
char *s;
int hide = 0;
/* If non-presence container && HIDE mode && only child is
* a list, then skip container keyword
* See also xml2cli
*/
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){
@ -714,11 +723,13 @@ yang2cli_container(clicon_handle h,
if (cli_callback_generate(h, ys, cb) < 0)
goto done;
cprintf(cb, ";{\n");
}
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;
if (hide == 0)
cprintf(cb, "%*s}\n", level*3, "");
retval = 0;
done:
@ -732,6 +743,7 @@ yang2cli_container(clicon_handle h,
* @param[in] ys Yang statement
* @param[in] gt CLI Generate style
* @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
@ -739,6 +751,7 @@ yang2cli_list(clicon_handle h,
yang_stmt *ys,
enum genmodel_type gt,
int level,
int state,
cbuf *cb)
{
yang_stmt *yc;
@ -775,7 +788,8 @@ yang2cli_list(clicon_handle h,
/* Print key variable now, and skip it in loop below
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)
goto done;
}
@ -794,7 +808,7 @@ yang2cli_list(clicon_handle h,
}
if (cvi != NULL)
continue;
if (yang2cli_stmt(h, yc, gt, level+1, cb) < 0)
if (yang2cli_stmt(h, yc, gt, level+1, state, cb) < 0)
goto done;
}
cprintf(cb, "%*s}\n", level*3, "");
@ -811,6 +825,7 @@ yang2cli_list(clicon_handle h,
* @param[in] ys Yang statement
* @param[in] gt CLI Generate style
* @param[in] level Indentation level
* @param[in] state Include syntax for state not only config
* @param[out] cb Buffer where cligen code is written
@example
choice interface-type {
@ -826,6 +841,7 @@ yang2cli_choice(clicon_handle h,
yang_stmt *ys,
enum genmodel_type gt,
int level,
int state,
cbuf *cb)
{
int retval = -1;
@ -835,7 +851,7 @@ yang2cli_choice(clicon_handle h,
while ((yc = yn_each(ys, yc)) != NULL) {
switch (yang_keyword_get(yc)){
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;
break;
case Y_CONTAINER:
@ -843,7 +859,7 @@ yang2cli_choice(clicon_handle h,
case Y_LEAF_LIST:
case Y_LIST:
default:
if (yang2cli_stmt(h, yc, gt, level+1, cb) < 0)
if (yang2cli_stmt(h, yc, gt, level+1, state, cb) < 0)
goto done;
break;
}
@ -856,32 +872,34 @@ yang2cli_choice(clicon_handle h,
/*! Generate CLI code for Yang statement
* @param[in] h Clixon handle
* @param[in] ys Yang statement
* @param[out] cb Buffer where cligen code is written
* @param[in] gt CLI Generate style
* @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
yang2cli_stmt(clicon_handle h,
yang_stmt *ys,
enum genmodel_type gt,
int level, /* indentation level for pretty-print */
int level,
int state,
cbuf *cb)
{
yang_stmt *yc;
int retval = -1;
if (yang_config(ys)){
if (state || yang_config(ys)){
switch (yang_keyword_get(ys)){
case Y_CONTAINER:
if (yang2cli_container(h, ys, gt, level, cb) < 0)
if (yang2cli_container(h, ys, gt, level, state, cb) < 0)
goto done;
break;
case Y_LIST:
if (yang2cli_list(h, ys, gt, level, cb) < 0)
if (yang2cli_list(h, ys, gt, level, state, cb) < 0)
goto done;
break;
case Y_CHOICE:
if (yang2cli_choice(h, ys, gt, level, cb) < 0)
if (yang2cli_choice(h, ys, gt, level, state, cb) < 0)
goto done;
break;
case Y_LEAF_LIST:
@ -894,7 +912,7 @@ yang2cli_stmt(clicon_handle h,
case Y_MODULE:
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;
break;
default: /* skip */
@ -911,6 +929,7 @@ yang2cli_stmt(clicon_handle h,
* @param[in] yspec Yang specification
* @param[in] gt CLI Generate style
* @param[in] printgen Log generated CLIgen syntax
* @param[in] state Also include state syntax
* @param[out] ptnew CLIgen parse-tree
*
* Code generation styles:
@ -922,6 +941,7 @@ yang2cli(clicon_handle h,
yang_stmt *yspec,
enum genmodel_type gt,
int printgen,
int state,
parse_tree *ptnew)
{
cbuf *cb = NULL;
@ -936,7 +956,7 @@ yang2cli(clicon_handle h,
/* Traverse YANG, loop through all modules and generate CLI */
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;
if (printgen)
clicon_log(LOG_NOTICE, "%s: Generated CLI spec:\n%s", __FUNCTION__, cbuf_get(cb));
@ -951,9 +971,9 @@ yang2cli(clicon_handle h,
goto done;
cvec_free(globals);
/* Resolve the expand callback functions in the generated syntax.
This "should" only be GENERATE_EXPAND_XMLDB
handle=NULL for global namespace, this means expand callbacks must be in
CLICON namespace, not in a cli frontend plugin.
* This "should" only be GENERATE_EXPAND_XMLDB
* handle=NULL for global namespace, this means expand callbacks must be in
* CLICON namespace, not in a cli frontend plugin.
*/
if (cligen_expandv_str2fn(ptnew, (expandv_str2fn_t*)clixon_str2fn, NULL) < 0)
goto done;

View file

@ -2,7 +2,8 @@
*
***** 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.
@ -36,10 +37,22 @@
#ifndef _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
*/
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_ */

View file

@ -236,6 +236,103 @@ cli_interactive(clicon_handle h)
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
usage(clicon_handle h,
char *argv0)
@ -294,6 +391,7 @@ main(int argc,
cvec *nsctx_global = NULL; /* Global namespace context */
size_t cligen_buflen;
size_t cligen_bufthreshold;
int dbg=0;
/* Defaults */
once = 0;
@ -332,7 +430,7 @@ main(int argc,
help = 1;
break;
case 'D' : /* debug */
if (sscanf(optarg, "%d", &debug) != 1)
if (sscanf(optarg, "%d", &dbg) != 1)
usage(h, argv[0]);
break;
case 'f': /* config file */
@ -352,9 +450,9 @@ main(int argc,
/*
* 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 */
if (clicon_options_main(h) < 0){
@ -535,29 +633,9 @@ main(int argc,
if (clicon_nsctx_global_set(h, nsctx_global) < 0)
goto done;
/* Create tree generated from dataspec. If no other trees exists, this is
* the only one.
* The following code creates the tree @datamodel
* 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");
/* Create autocli from YANG */
if (autocli_start(h, printgen) < 0)
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 */
if (cli_syntax_load(h) < 0)
@ -588,8 +666,8 @@ main(int argc,
if (logclisyntax)
cli_logsyntax_set(h, logclisyntax);
if (debug)
clicon_option_dump(h, debug);
if (dbg)
clicon_option_dump(h, dbg);
/* Join rest of argv to a single command */
restarg = clicon_strjoin(argc, argv, " ");

View file

@ -66,6 +66,7 @@
#include "clixon_cli_api.h"
#include "cli_plugin.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[out] error Static error string, if set indicates error
* @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
* @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;
/* Reset error */
*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 */
if (handle) {

View file

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

View file

@ -197,7 +197,7 @@ netconf_output(int s,
int retval = -1;
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;
if (clixon_xml_parse_string(buf, YB_NONE, NULL, &xt, NULL) == 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 */
size_t cligen_buflen;
size_t cligen_bufthreshold;
int dbg = 0;
/* Create handle */
if ((h = clicon_handle_init()) == NULL)
@ -421,7 +422,7 @@ main(int argc,
usage(h, argv[0]);
break;
case 'D' : /* debug */
if (sscanf(optarg, "%d", &debug) != 1)
if (sscanf(optarg, "%d", &dbg) != 1)
usage(h, argv[0]);
break;
case 'f': /* override config file */
@ -442,8 +443,8 @@ main(int argc,
/*
* Logs, error and debug to stderr or syslog, set debug level
*/
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, logdst);
clicon_debug_init(debug, NULL);
clicon_log_init(__PROGRAM__, dbg?LOG_DEBUG:LOG_INFO, logdst);
clicon_debug_init(dbg, NULL);
/* Find, read and parse configfile */
if (clicon_options_main(h) < 0)
@ -592,8 +593,8 @@ main(int argc,
send_hello(h, 1, id);
if (clixon_event_reg_fd(0, netconf_input_cb, h, "netconf socket") < 0)
goto done;
if (debug)
clicon_option_dump(h, debug);
if (dbg)
clicon_option_dump(h, dbg);
if (tv.tv_sec || tv.tv_usec){
struct timeval t;
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@
# Applications
ifeq ($(with_restconf),fcgi)
APPL = clixon_restconf # fcgi / nginx
else
APPL = clixon_restconf_$(with_restconf)
endif
# Application
APPL = clixon_restconf
# 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
ifeq ($(with_restconf),fcgi)
APPSRC = restconf_fcgi_lib.c
APPSRC += restconf_fcgi_main.c
APPSRC += restconf_methods.c # These should be moved ^
APPSRC += restconf_fcgi_lib.c # Most of these should be made generic
APPSRC += restconf_methods.c
APPSRC += restconf_methods_post.c
APPSRC += restconf_methods_get.c
APPSRC += restconf_stream.c
endif
# Evhtp-specific source including main
ifeq ($(with_restconf),evhtp)
APPSRC = restconf_evhtp_main.c
endif
APPOBJ = $(APPSRC:.c=.o)
# 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
LIBOBJ = $(LIBSRC:.c=.o)
# 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

@ -32,6 +32,7 @@
***** END LICENSE BLOCK *****
* libevhtp code
*/
/* XXX temp constant should go away, */
@ -43,7 +44,7 @@
/* compilation withotu threading support
* XXX: could be disabled already in configure?
*/
#define EVHTP_DISABLE_EVTHR
//#define EVHTP_DISABLE_EVTHR
#define EVHTP_DISABLE_REGEX
#include <stdlib.h>
@ -73,13 +74,9 @@
/* restconf */
#include "restconf_lib.h"
#if 0 /* These are all dependent on FCGX */
#include "restconf_methods.h"
#include "restconf_methods_get.h"
#include "restconf_methods_post.h"
#include "restconf_stream.h"
#endif
#include "restconf_lib.h" /* generic shared with plugins */
#include "restconf_api.h" /* generic not shared with plugins */
#include "restconf_root.h"
/* Command line options to be passed to getopt(3) */
#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
cx_gencb(evhtp_request_t *req,
void *arg)
@ -129,7 +298,7 @@ cx_gencb(evhtp_request_t *req,
evhtp_connection_t *conn;
// clicon_handle h = arg;
fprintf(stderr, "%s\n", __FUNCTION__);
clicon_debug(1, "%s", __FUNCTION__);
if (req == NULL){
errno = EINVAL;
return;
@ -145,67 +314,105 @@ cx_gencb(evhtp_request_t *req,
return; /* void */
}
static evhtp_res
cx_pre_accept(evhtp_connection_t *conn,
/*! /.well-known callback
* @see cx_genb
*/
static void
cx_path_wellknown(evhtp_request_t *req,
void *arg)
{
fprintf(stderr, "%s\n", __FUNCTION__);
return EVHTP_RES_OK;
clicon_handle h = arg;
/* 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
cx_post_accept(evhtp_connection_t *conn,
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
/*! /restconf callback
* @see cx_genb
*/
static void
cx_path_restconf(evhtp_request_t *req,
void *arg)
{
evhtp_connection_t *conn;
// clicon_handle h = arg;
clicon_handle h = arg;
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){
errno = EINVAL;
goto done;
}
if ((conn = evhtp_request_get_connection(req)) == NULL)
goto done;
meth = evhtp_request_get_method(req);
fprintf(stderr, "%s method:%d\n", __FUNCTION__, meth);
evhtp_headers_for_each(req->headers_in, print_header_, NULL);
/* input debug */
if (clicon_debug_get())
evhtp_headers_for_each(req->headers_in, print_header, h);
if ((b = evbuffer_new()) == NULL){
if ((cblen = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
htp_sslutil_add_xheaders(
req->headers_out,
conn->ssl,
HTP_SSLUTILS_XHDR_ALL);
/* Query vector, ie the ?a=x&b=y stuff */
if ((qvec = cvec_new(0)) ==NULL){
clicon_err(OE_UNIX, errno, "cvec_new");
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);
evbuffer_add(b, "hej\n", strlen("hej\n\n"));
evhtp_send_reply_body(req, b);
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:
if (qvec)
cvec_free(qvec);
if (cblen)
cbuf_free(cblen);
if (b)
evhtp_safe_free(b, evbuffer_free);
return; /* void */
}
@ -266,6 +473,7 @@ main(int argc,
struct event_base *evbase = NULL;
evhtp_ssl_cfg_t *ssl_config = NULL;
struct stat f_stat;
int dbg = 0;
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
@ -282,7 +490,7 @@ main(int argc,
usage(h, argv0);
break;
case 'D' : /* debug */
if (sscanf(optarg, "%d", &debug) != 1)
if (sscanf(optarg, "%d", &dbg) != 1)
usage(h, argv0);
break;
case 'f': /* override config file */
@ -303,9 +511,9 @@ main(int argc,
/*
* 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());
if (set_signal(SIGTERM, restconf_sig_term, NULL) < 0){
clicon_err(OE_DAEMON, errno, "Setting signal");
@ -390,7 +598,6 @@ main(int argc,
}
argc -= optind;
argv += optind;
/* Check ssl mandatory options */
if (ssl_config->pemfile == NULL || ssl_config->privfile == NULL)
usage(h, argv0);
@ -412,6 +619,53 @@ main(int argc,
/* Access the remaining argv/argc options (after --) w clicon-argv_get() */
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 */
cligen_buflen = clicon_option_int(h, "CLICON_CLI_BUF_START");
cligen_bufthreshold = clicon_option_int(h, "CLICON_CLI_BUF_THRESHOLD");
@ -422,7 +676,6 @@ main(int argc,
*/
if (netconf_module_features(h) < 0)
goto done;
/* Create top-level yang spec and store as option */
if ((yspec = yspec_new()) == NULL)
goto done;
@ -491,8 +744,8 @@ main(int argc,
goto done;
/* Dump configuration options on debug */
if (debug)
clicon_option_dump(h, debug);
if (dbg)
clicon_option_dump(h, dbg);
/* Call start function in all plugins before we go interactive
*/
@ -503,40 +756,6 @@ main(int argc,
if (clicon_options_main(h) < 0)
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);

View file

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

View file

@ -50,11 +50,11 @@ int restconf_unsupported_media(FCGX_Request *r);
int restconf_internal_server_error(clicon_handle h, FCGX_Request *r);
int restconf_notimplemented(FCGX_Request *r);
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);
cbuf *readdata(FCGX_Request *r);
int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr,
int pretty, enum restconf_media media, int code);
int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr, int pretty, restconf_media media, int code0);
int http_location(clicon_handle h, FCGX_Request *r, cxobj *xobj);
#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 */
/* restconf */
#include "restconf_lib.h"
#include "restconf_fcgi_lib.h"
#include "restconf_methods.h"
#include "restconf_lib.h" /* generic shared with plugins */
#include "restconf_api.h" /* generic not shared with plugins */
#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_post.h"
#include "restconf_stream.h"
@ -101,7 +104,7 @@
*/
static int
api_data(clicon_handle h,
FCGX_Request *r,
FCGX_Request *req,
char *api_path,
cvec *pcvec,
int pi,
@ -117,21 +120,21 @@ api_data(clicon_handle h,
request_method = clixon_restconf_param_get(h, "REQUEST_METHOD");
clicon_debug(1, "%s method:%s", __FUNCTION__, request_method);
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)
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)
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)
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)
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)
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)
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
retval = restconf_notfound(h, r);
retval = restconf_notfound(h, req);
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval;
}
@ -181,27 +184,44 @@ api_operations(clicon_handle h,
* In line with the best practices defined by [RFC7320], RESTCONF
* enables deployments to specify where the RESTCONF API is located.
*/
#if 0
static int
api_well_known(clicon_handle h,
FCGX_Request *r)
FCGX_Request *req)
{
clicon_debug(1, "%s", __FUNCTION__);
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
FCGX_FPrintF(r->out, "Content-Type: application/xrd+xml\r\n");
FCGX_FPrintF(r->out, "\r\n");
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>\n");
FCGX_FPrintF(r->out, " <Link rel='restconf' href='/restconf'/>\n");
FCGX_FPrintF(r->out, "</XRD>\r\n");
char *request_method;
FCGX_Request *body;
/* call generic function */
if (api_well_known(h, req) < 0)
goto done;
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;
}
#endif
/*! Retrieve the Top-Level API Resource
* @param[in] h Clicon handle
* @param[in] r Fastcgi request handle
* @note Only returns null for operations and data,...
* See RFC8040 3.3
* XXX doesnt check method
*/
static int
api_root(clicon_handle h,
@ -318,11 +338,11 @@ api_yang_library_version(clicon_handle h,
*/
static int
api_restconf(clicon_handle h,
FCGX_Request *r)
FCGX_Request *req)
{
int retval = -1;
char *path;
char *query;
char *query = NULL;
char *method;
char **pvec = NULL;
int pn;
@ -357,7 +377,7 @@ api_restconf(clicon_handle h,
if (strcmp(media_str, "*/*") == 0) /* catch-all */
media_out = YANG_DATA_JSON;
else{
retval = restconf_unsupported_media(r);
retval = restconf_unsupported_media(req);
goto done;
}
}
@ -367,34 +387,35 @@ api_restconf(clicon_handle h,
goto done;
/* Sanity check of path. Should be /restconf/ */
if (pn < 2){
restconf_notfound(h, r);
restconf_notfound(h, req);
goto ok;
}
if (strlen(pvec[0]) != 0){
retval = restconf_notfound(h, r);
retval = restconf_notfound(h, req);
goto done;
}
if (strcmp(pvec[1], RESTCONF_API)){
retval = restconf_notfound(h, r);
retval = restconf_notfound(h, req);
goto done;
}
restconf_test(r, 1);
restconf_test(req, 1);
if (pn == 2){
retval = api_root(h, r, pretty, media_out);
retval = api_root(h, req, pretty, media_out);
goto done;
}
if ((method = pvec[2]) == NULL){
retval = restconf_notfound(h, r);
retval = restconf_notfound(h, req);
goto done;
}
clicon_debug(1, "%s: method=%s", __FUNCTION__, method);
if (query != NULL && strlen(query))
if (str2cvec(query, '&', '=', &qvec) < 0)
goto done;
if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */
goto done;
/* data */
if ((cb = readdata(r)) == NULL)
if ((cb = readdata(req)) == NULL)
goto done;
data = cbuf_get(cb);
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
* 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;
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)
goto done;
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 ok;
}
@ -425,23 +446,23 @@ api_restconf(clicon_handle h,
}
clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
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;
}
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)
goto done;
}
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)
goto done;
}
else if (strcmp(method, "test") == 0)
restconf_test(r, 0);
restconf_test(req, 0);
else
restconf_notfound(h, r);
restconf_notfound(h, req);
ok:
retval = 0;
done:
@ -534,7 +555,7 @@ main(int argc,
int sock;
char *argv0 = argv[0];
FCGX_Request request;
FCGX_Request *r = &request;
FCGX_Request *req = &request;
int c;
char *sockpath;
char *path;
@ -551,6 +572,7 @@ main(int argc,
cvec *nsctx_global = NULL; /* Global namespace context */
size_t cligen_buflen;
size_t cligen_bufthreshold;
int dbg = 0;
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
@ -567,7 +589,7 @@ main(int argc,
usage(h, argv[0]);
break;
case 'D' : /* debug */
if (sscanf(optarg, "%d", &debug) != 1)
if (sscanf(optarg, "%d", &dbg) != 1)
usage(h, argv[0]);
break;
case 'f': /* override config file */
@ -587,9 +609,9 @@ main(int argc,
/*
* 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());
if (set_signal(SIGTERM, restconf_sig_term, NULL) < 0){
clicon_err(OE_DAEMON, errno, "Setting signal");
@ -663,6 +685,7 @@ main(int argc,
cligen_bufthreshold = clicon_option_int(h, "CLICON_CLI_BUF_THRESHOLD");
cbuf_alloc_set(cligen_buflen, cligen_bufthreshold);
/* Add (hardcoded) netconf features in case ietf-netconf loaded here
* Otherwise it is loaded in netconf_module_load below
*/
@ -737,8 +760,8 @@ main(int argc,
goto done;
/* Dump configuration options on debug */
if (debug)
clicon_option_dump(h, debug);
if (dbg)
clicon_option_dump(h, dbg);
/* Call start function in all plugins before we go interactive
*/
@ -758,7 +781,21 @@ main(int argc,
clicon_err(OE_CFG, errno, "FCGX_OpenSocket");
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)
goto done;
/* 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");
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");
goto done;
}
while (1) {
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");
goto done;
}
@ -793,32 +834,32 @@ main(int argc,
/* Translate from FCGI parameter form to Clixon runtime data
* XXX: potential name collision?
*/
if (clixon_restconf_params_set(h, r->envp) < 0)
if (clixon_restconf_params_set(h, req->envp) < 0)
goto done;
if ((path = clixon_restconf_param_get(h, "REQUEST_URI")) != NULL){
clicon_debug(1, "path: %s", path);
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) {
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) {
api_well_known(h, r); /* */
api_well_known(h, req); /* */
}
else{
clicon_debug(1, "top-level %s not found", path);
restconf_notfound(h, r);
restconf_notfound(h, req);
}
}
else
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;
if (finish)
FCGX_Finish_r(r);
FCGX_Finish_r(req);
else{ /* A handler is forked so we initiate a new request after instead
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");
goto done;
}

View file

@ -59,6 +59,7 @@
/* clicon */
#include <clixon/clixon.h>
#include "restconf_api.h"
#include "restconf_lib.h"
/* 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);
}
/*! 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
clixon_restconf_param_del(clicon_handle h,
char *param)
@ -469,3 +477,78 @@ restconf_uripath(clicon_handle h)
*q = '\0';
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_
/*
* 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
* @see http_media_map
* (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_del(clicon_handle h, char *param);
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_ */

View file

@ -306,7 +306,7 @@ api_data_write(clicon_handle h,
}
#if 0
if (debug)
if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, xret, "%s xret:", __FUNCTION__);
#endif
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.
*/
#if 0 /* DEBUG */
if (debug)
if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, xret, "%s xret:", __FUNCTION__);
#endif
/* Check if error return */

View file

@ -307,7 +307,7 @@ api_data_post(clicon_handle h,
if (restconf_insert_attributes(xdata, qvec) < 0)
goto done;
#if 1
if (debug)
if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, xdata, "%s xdata:", __FUNCTION__);
#endif
@ -512,7 +512,7 @@ api_operations_post_input(clicon_handle h,
* <data><input xmlns="urn:example:clixon">...</input></data>
*/
#if 1
if (debug)
if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, xdata, "%s xdata:", __FUNCTION__);
#endif
/* Validate that exactly only <input> tag */
@ -614,7 +614,7 @@ api_operations_post_output(clicon_handle h,
xml_name_set(xoutput, "output");
/* xoutput should now look: <output><x xmlns="uri">0</x></output> */
#if 1
if (debug)
if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, xoutput, "%s xoutput:", __FUNCTION__);
#endif
@ -843,7 +843,7 @@ api_operations_post(clicon_handle h,
/* Here xtop is:
<rpc username="foo"><myfn xmlns="uri"><x>42</x></myfn></rpc> */
#if 1
if (debug)
if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, xtop, "%s 5. Translate input args:", __FUNCTION__);
#endif
/* 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>
*/
#if 0
if (debug)
if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, xtop, "%s 6. Validate and defaults:", __FUNCTION__);
#endif
/* 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>
*/
#if 1
if (debug)
if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, xret, "%s Receive reply:", __FUNCTION__);
#endif
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
if test "x${with_restconf}" != "x"; then
# This is for changing web user default www-data
# Should this be a runtime option?
AC_ARG_WITH([wwwuser],
[AS_HELP_STRING([--with-wwwuser=<user>],[Set www user different from www-data])])
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)
## 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?
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?
Clixon:
@ -128,8 +128,7 @@ clicon:x:1001:<user>,www-data
## How do I use the CLI?
The easiest way to use Clixon is via the CLI. Once the backend is started
Example:
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:
```
clixon_cli -f /usr/local/etc/example.xml
cli> set interfaces interface eth9 ?

View file

@ -159,7 +159,7 @@ main_commit(clicon_handle h,
/* Get all added i/fs */
if (xpath_vec_flag(target, nsc, "//interface", XML_FLAG_ADD, &vec, &len) < 0)
return -1;
if (debug)
if (clicon_debug_get())
for (i=0; i<len; i++) /* Loop over added i/fs */
xml_print(stdout, vec[i]); /* Print the added interface */
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", "/");{
@datamodel, cli_show_auto("candidate", "xml");
}
cli("Show configuration as CLI commands"), cli_show_config("candidate", "cli", "/");{
@datamodel, cli_show_auto("candidate", "cli");
cli("Show configuration as CLI commands"), cli_show_config("candidate", "cli", "/", 0, "set ");{
@datamodel, cli_show_auto("candidate", "cli", "set ");
}
netconf("Show configuration as netconf edit-config operation"), cli_show_config("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_FILE 8 /* print logs on clicon_log_filename */
/*
* Variables
*/
extern int debug;
/*
* Prototypes
*/
@ -67,6 +62,8 @@ int clicon_log(int level, char *format, ...);
int clicon_debug(int dbglevel, char *format, ...);
#endif
int clicon_debug_init(int dbglevel, FILE *f);
int clicon_debug_get(void);
char *mon2name(int md);
#endif /* _CLIXON_LOG_H_ */

View file

@ -58,20 +58,24 @@
*/
/*! Controls how keywords a generated in CLI syntax / prints from object model
* Example YANG:
* container c{
* list a {
* key x;
* leaf x;
* leaf y;
* }
* NONE: a <x> <y>;
* VARS: a <x> y <y>;
* ALL: a x <x> y <y>;
* }
* NONE: c a <x> <y>;
* VARS: c a <x> y <y>;
* ALL: c a x <x> y <y>;
* HIDE: a x <x> y <y>;
*/
enum genmodel_type{
GT_ERR =-1, /* Error */
GT_NONE=0, /* No extra keywords */
GT_VARS, /* Keywords on non-key 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 */

View file

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

View file

@ -58,8 +58,12 @@
#include "clixon_err.h"
#include "clixon_log.h"
/* The global debug level. 0 means no debug */
int debug = 0;
/* The global debug level. 0 means no debug
* @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 */
static int _logflags = 0x0;
@ -67,6 +71,7 @@ static int _logflags = 0x0;
/* Set to open file to write debug messages directly to file */
static FILE *_logfile = NULL;
/*! Initialize system logger.
*
* 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:
* if normal (not debug) then filter loglevels >= debug
*/
if (debug == 0 && level >= LOG_DEBUG)
if (_clixon_debug == 0 && level >= LOG_DEBUG)
goto done;
if (_logflags & CLICON_LOG_STDERR){
flogtime(stderr);
@ -231,6 +236,7 @@ clicon_log_str(int level,
flogtime(_logfile);
fprintf(_logfile, "%s\n", msg);
fflush(_logfile);
}
/* Enable this if you want syslog in a stream. But there are problems with
@ -288,7 +294,6 @@ clicon_log(int level,
return retval;
}
/*! Initialize debug messages. Set debug level.
*
* 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,
FILE *f)
{
debug = dbglevel; /* Global variable */
_clixon_debug = dbglevel; /* Global variable */
return 0;
}
int
clicon_debug_get(void)
{
return _clixon_debug;
}
/*! 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
@ -335,7 +346,7 @@ clicon_debug(int dbglevel,
char *msg = NULL;
int retval = -1;
if (dbglevel > debug) /* debug mask */
if (dbglevel > _clixon_debug) /* compare debug mask with global variable */
return 0;
/* first round: compute length of debug message */
va_start(args, format);

View file

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

View file

@ -85,6 +85,7 @@ static const map_str2int cli_genmodel_map[] = {
{"NONE", GT_NONE},
{"VARS", GT_VARS},
{"ALL", GT_ALL},
{"HIDE", GT_HIDE},
{NULL, -1}
};
@ -612,7 +613,7 @@ clicon_option_del(clicon_handle h,
* But sometimes there are type conversions, etc which makes it more
* 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().
* @param[in] h Clicon handle
* @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 */
if (api_path_parse(api_path, &cplist) < 0)
goto done;
if (debug)
if (clicon_debug_get())
clixon_path_print(stderr, cplist);
/* Resolve module:name to yang-stmt, fail if not successful */
if ((ret = api_path_resolve(cplist, yt)) < 0)
@ -1652,7 +1652,7 @@ clixon_xml_find_instance_id(cxobj *xt,
va_end(ap);
if (instance_id_parse(path, &cplist) < 0)
goto done;
if (debug)
if (clicon_debug_get())
clixon_path_print(stderr, cplist);
/* Resolve module:name to pointer to yang-stmt, fail if not successful */
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",
__FUNCTION__, ntohl(msg->op_len));
if (debug > 2)
if (clicon_debug_get() > 2)
msg_dump(msg);
if (atomicio((ssize_t (*)(int, void *, size_t))write,
s, msg, ntohl(msg->op_len)) < 0){
@ -400,7 +400,7 @@ clicon_msg_rcv(int s,
clicon_err(OE_CFG, errno, "body too short");
goto done;
}
if (debug > 1)
if (clicon_debug_get() > 1)
msg_dump(*msg);
retval = 0;
done:

View file

@ -927,7 +927,7 @@ url_post(char *url,
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(postfields));
if (debug)
if (clicon_debug_get())
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
if ((errcode = curl_easy_perform(curl)) != CURLE_OK){
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 (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));
if ((body = xml_body(x)) != NULL){
if (index(body, ' '))
@ -224,6 +224,11 @@ xml2cli(FILE *f,
}
if (prepend0)
cprintf(cbpre, "%s", prepend0);
/* 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){
@ -358,7 +363,7 @@ xml2cvec(cxobj *xt,
}
}
}
if (debug > 1){
if (clicon_debug_get() > 1){
clicon_debug(2, "%s cvv:\n", __FUNCTION__);
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)");
goto done;
}
if (debug > 1){
if (clicon_debug_get() > 1){
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;

View file

@ -940,7 +940,7 @@ xp_eval(xp_ctx *xc,
xp_ctx *xr2 = NULL;
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));
/* Pre-actions before check first child c0
*/
@ -1096,7 +1096,7 @@ xp_eval(xp_ctx *xc,
xr0 = NULL;
}
ok:
if (debug>1)
if (clicon_debug_get() > 1)
ctx_print(stderr, *xrp, xpath_tree_int2str(xs->xs_type));
retval = 0;
done:

View file

@ -2537,6 +2537,54 @@ yang_arg2cvec(yang_stmt *ys,
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
*
* The function looks at the LIST argument string (not actual children)

View file

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

View file

@ -90,9 +90,6 @@ fi
# RESTCONF protocol, eg http or https
: ${RCPROTO:=http}
# RESTCONF port
: ${RCPORT:=80}
# RESTCONF error message (if not up)
: ${RCERROR:="HTTP/1.1 502 Bad Gateway"}
@ -229,8 +226,11 @@ wait_backend(){
done
}
# Start restconf daemon
# @see wait_restconf
start_restconf(){
# Start in background
# echo "sudo -u $wwwuser -s $clixon_restconf $RCLOG -D $DBG $*"
sudo -u $wwwuser -s $clixon_restconf $RCLOG -D $DBG $* &
if [ $? -ne 0 ]; then
err
@ -246,12 +246,16 @@ stop_restconf(){
}
# Wait for restconf to stop sending 502 Bad Gateway
# @see start_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;
while [[ $hdr == "$RCERROR"* ]]; do
while [[ $hdr != *"200 OK"* ]]; do
sleep 1
hdr=$(curl --head -sS http://localhost/restconf)
hdr=$(curl -kis $RCPROTO://localhost/restconf)
# echo "hdr:\"$hdr\""
let i++;
# echo "wait_restconf $i"
if [ $i -ge $RCWAIT ]; then
@ -366,6 +370,8 @@ expecteq(){
# - expected stdout outcome*
# - the token "--not--"
# - 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 \[\]
expectpart(){
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 "^$"
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"
expectfn "$clixon_cli -1 -f $cfg -l o set system protocol tcp" 0 "^$"
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"
expectfn "$clixon_cli -1 -f $cfg -l o delete all" 0 "^$"

View file

@ -49,24 +49,25 @@ if [ $BE -ne 0 ]; then
fi
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)"
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"
expectfn "$clixon_cli -1 -f $cfg delete interfaces" 0 "^$"
expectpart "$($clixon_cli -1 -f $cfg delete interfaces)" 0 "^$"
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"
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"
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 <&"
# problems in changing to expectpart with escapes
expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 description \"foo<&bar\"" 0 ""
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>"
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"
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"
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"
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"
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"
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"
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"
expectfn "$clixon_cli -1 -f $cfg -l o validate" 0 "^$"
expectpart "$($clixon_cli -1 -f $cfg -l o validate)" 0 "^$"
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"
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"
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"
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"
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"
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?
new "cli debug reset"
expectfn "$clixon_cli -1 -f $cfg -l o debug level 0" 0 "^$"
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
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
# 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
<sender-state xmlns="urn:example:example">
<ref>x</ref>

View file

@ -57,37 +57,37 @@ if [ $RC -ne 0 ]; then
new "start restconf daemon"
start_restconf -f $cfg
new "waiting"
wait_restconf
fi
new "waiting"
wait_restconf
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)"
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)"
# 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
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)"
ret=$(curl -s -X GET -H "Accept: application/yang-data+xml" http://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>'
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"/></operations>'
match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then
err "$expect" "$ret"
fi
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)"
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>"
match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then
@ -95,46 +95,46 @@ if [ -z "$match" ]; then
fi
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"
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
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"
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"
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"
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)"
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)"
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
#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"
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"
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'
#'{"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"
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>'
match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then
@ -142,12 +142,12 @@ if [ -z "$match" ]; then
fi
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"
# 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>'
match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then
@ -155,12 +155,12 @@ if [ -z "$match" ]; then
fi
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"
# 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>'
match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then
@ -168,94 +168,94 @@ if [ -z "$match" ]; then
fi
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
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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)"
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"
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"
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"
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"
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"
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"
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"
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
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
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"
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"
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"
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"
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>'
match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then
@ -263,10 +263,10 @@ if [ -z "$match" ]; then
fi
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"
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>'
match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then
@ -274,10 +274,10 @@ if [ -z "$match" ]; then
fi
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)"
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
new "Kill restconf daemon"

View file

@ -94,22 +94,22 @@ if [ $RC -ne 0 ]; then
fi
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"
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"
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"
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"
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"
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>'
match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then
@ -117,83 +117,83 @@ if [ -z "$match" ]; then
fi
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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)"
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"
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
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"
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
new "Kill restconf daemon"

View file

@ -169,54 +169,54 @@ if [ $RC -ne 0 ]; then
fi
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"
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"
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"
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"
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"
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"
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"
# 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
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"
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"
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"
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"
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
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
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
new "Kill restconf daemon"

View file

@ -89,160 +89,160 @@ if [ $RC -ne 0 ]; then
fi
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"}}'
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)"
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
# seems wrong to recreate
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"
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>'
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)"
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)"
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)"
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"
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"
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)"
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
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)"
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"
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"
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"
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"
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)"
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"
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
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)'
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'
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'
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'
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"
#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'
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'
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'
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
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"
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.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']\"}]}"
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)'
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'
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"
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']\"}]}"
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)'
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)'
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)'
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"
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)'
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'
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'
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'
#- 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)'
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'
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)'
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.3.3. "fields" Parameter'

View file

@ -92,78 +92,78 @@ if [ $RC -ne 0 ]; then
fi
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
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)"
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"
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"
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)"
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)"
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)"
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"
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)"
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"
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"
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)"
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"
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)"
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"
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)"
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"
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"
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"
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)"
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"
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)"
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
new "Kill restconf daemon"

View file

@ -119,80 +119,80 @@ wait_restconf
# also in test_restconf.sh
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."
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"
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"
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)"
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)"
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.'
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'
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
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"
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"
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
# resource within the target resource.
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)"
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"
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"
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 ^)"
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)"
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"
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"
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
# method MUST NOT change the value of the leaf-list instance.
# leaf-list extra{
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"
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"
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"
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
# 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.
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"
stop_restconf

View file

@ -74,16 +74,16 @@ testrun(){
wait_restconf
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"
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"
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"
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"
stop_restconf

View file

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

View file

@ -96,6 +96,7 @@ main(int argc,
cxobj *xerr = NULL; /* malloced must be freed */
int ret;
int pretty = 0;
int dbg = 0;
optind = 1;
opterr = 0;
@ -105,7 +106,7 @@ main(int argc,
usage(argv[0]);
break;
case 'D':
if (sscanf(optarg, "%d", &debug) != 1)
if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv[0]);
break;
case 'j':
@ -125,7 +126,9 @@ main(int argc,
usage(argv[0]);
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 ((yspec = yspec_new()) == NULL)
goto done;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -148,16 +148,19 @@ module clixon-config {
typedef cli_genmodel_type{
description
"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{
enum NONE{
description "No extra keywords: a <x> <y>";
description "No extra keywords: c a <x> <y>";
}
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{
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;
default 1;
description
"If set, generate CLI specification for CLI completion of
loaded Yang modules. This CLI tree can be accessed in CLI
spec files using the tree reference syntax (eg @datamodel).
See also CLICON_CLI_MODEL_TREENAME.
(consider boolean)";
"0: Do not generate CLISPEC syntax for the auto-cli.
1: Generate a CLI specification for CLI completion of all loaded Yang modules.
This CLI tree can be accessed in CLI-spec files using the tree reference syntax (eg
@datamodel).
2: Same including state syntax in a tree called @datamodelstate.
See also CLICON_CLI_MODEL_TREENAME.";
}
leaf CLICON_CLI_MODEL_TREENAME {
type string;
@ -411,7 +415,9 @@ module clixon-config {
description
"If set, CLI specs can reference the
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 {
type int32;
@ -595,7 +601,8 @@ module clixon-config {
description
"If set, tag datastores with RFC 7895 YANG Module Library
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 {
type boolean;
@ -669,9 +676,11 @@ module clixon-config {
leaf CLICON_MODULE_LIBRARY_RFC7895 {
type boolean;
default true;
description "Enable RFC 7895 YANG Module library support as state
data. If enabled, module info will appear when doing
netconf get or restconf GET";
description
"Enable RFC 7895 YANG Module library support as state data. If
enabled, module info will appear when doing netconf get or
restconf GET.
See also CLICON_XMLDB_MODSTATE";
}
leaf CLICON_MODULE_SET_ID {
type string;