Merge branch 'master' of https://github.com/clicon/clixon
This commit is contained in:
commit
c18c40434f
71 changed files with 2181 additions and 713 deletions
14
CHANGELOG.md
14
CHANGELOG.md
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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,26 +703,34 @@ yang2cli_container(clicon_handle h,
|
|||
int retval = -1;
|
||||
char *helptext = NULL;
|
||||
char *s;
|
||||
int hide = 0;
|
||||
|
||||
cprintf(cb, "%*s%s", level*3, "", yang_argument_get(ys));
|
||||
if ((yd = yang_find(ys, Y_DESCRIPTION, NULL)) != NULL){
|
||||
if ((helptext = strdup(yang_argument_get(yd))) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "strdup");
|
||||
goto done;
|
||||
/* If 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){
|
||||
clicon_err(OE_UNIX, errno, "strdup");
|
||||
goto done;
|
||||
}
|
||||
if ((s = strstr(helptext, "\n\n")) != NULL)
|
||||
*s = '\0';
|
||||
cprintf(cb, "(\"%s\")", helptext);
|
||||
}
|
||||
if ((s = strstr(helptext, "\n\n")) != NULL)
|
||||
*s = '\0';
|
||||
cprintf(cb, "(\"%s\")", helptext);
|
||||
if (cli_callback_generate(h, ys, cb) < 0)
|
||||
goto done;
|
||||
cprintf(cb, ";{\n");
|
||||
}
|
||||
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;
|
||||
cprintf(cb, "%*s}\n", level*3, "");
|
||||
if (hide == 0)
|
||||
cprintf(cb, "%*s}\n", level*3, "");
|
||||
retval = 0;
|
||||
done:
|
||||
if (helptext)
|
||||
|
|
@ -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,45 +859,47 @@ 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;
|
||||
}
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! 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 */
|
||||
|
|
@ -902,7 +920,7 @@ yang2cli_stmt(clicon_handle h,
|
|||
}
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
|
@ -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,10 +971,10 @@ 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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
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);
|
||||
}
|
||||
/* Create autocli from YANG */
|
||||
if (autocli_start(h, printgen) < 0)
|
||||
goto done;
|
||||
|
||||
/* 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, " ");
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
66
apps/restconf/restconf_api.c
Normal file
66
apps/restconf/restconf_api.c
Normal 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"
|
||||
|
||||
54
apps/restconf/restconf_api.h
Normal file
54
apps/restconf/restconf_api.h
Normal 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_ */
|
||||
183
apps/restconf/restconf_api_evhtp.c
Normal file
183
apps/restconf/restconf_api_evhtp.c
Normal 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;
|
||||
}
|
||||
217
apps/restconf/restconf_api_fcgi.c
Normal file
217
apps/restconf/restconf_api_fcgi.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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,
|
||||
void *arg)
|
||||
/*! /.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 */
|
||||
}
|
||||
|
||||
|
|
@ -246,26 +453,27 @@ int
|
|||
main(int argc,
|
||||
char **argv)
|
||||
{
|
||||
int retval = -1;
|
||||
char *argv0 = argv[0];
|
||||
int c;
|
||||
clicon_handle h;
|
||||
char *dir;
|
||||
int logdst = CLICON_LOG_SYSLOG;
|
||||
yang_stmt *yspec = NULL;
|
||||
char *str;
|
||||
clixon_plugin *cp = NULL;
|
||||
cvec *nsctx_global = NULL; /* Global namespace context */
|
||||
size_t cligen_buflen;
|
||||
size_t cligen_bufthreshold;
|
||||
uint16_t port = 443;
|
||||
int retval = -1;
|
||||
char *argv0 = argv[0];
|
||||
int c;
|
||||
clicon_handle h;
|
||||
char *dir;
|
||||
int logdst = CLICON_LOG_SYSLOG;
|
||||
yang_stmt *yspec = NULL;
|
||||
char *str;
|
||||
clixon_plugin *cp = NULL;
|
||||
cvec *nsctx_global = NULL; /* Global namespace context */
|
||||
size_t cligen_buflen;
|
||||
size_t cligen_bufthreshold;
|
||||
uint16_t port = 443;
|
||||
#ifdef _EVHTP_NYI
|
||||
char *stream_path;
|
||||
char *stream_path;
|
||||
#endif
|
||||
evhtp_t *htp = NULL;
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -49,12 +49,12 @@ int restconf_conflict(FCGX_Request *r);
|
|||
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 restconf_test(FCGX_Request *r, int dbg);
|
||||
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_ */
|
||||
|
|
|
|||
|
|
@ -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 (str2cvec(query, '&', '=', &qvec) < 0)
|
||||
goto done;
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
118
apps/restconf/restconf_root.c
Normal file
118
apps/restconf/restconf_root.c
Normal 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;
|
||||
}
|
||||
|
||||
57
apps/restconf/restconf_root.h
Normal file
57
apps/restconf/restconf_root.h
Normal 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_ */
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ?
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -1305,8 +1305,9 @@ netconf_module_load(clicon_handle h)
|
|||
/* Load yang spec */
|
||||
if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec)< 0)
|
||||
goto done;
|
||||
if (yang_spec_parse_module(h, "clixon-rfc5277", 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 */
|
||||
if (clicon_option_bool(h, "CLICON_XML_CHANGELOG"))
|
||||
if (yang_spec_parse_module(h, "clixon-xml-changelog", NULL, yspec)< 0)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,7 +224,12 @@ xml2cli(FILE *f,
|
|||
}
|
||||
if (prepend0)
|
||||
cprintf(cbpre, "%s", prepend0);
|
||||
cprintf(cbpre, "%s ", xml_name(x));
|
||||
|
||||
/* If non-presence container && HIDE mode && only child is
|
||||
* a list, then skip container keyword
|
||||
* See also yang2cli_container */
|
||||
if (yang_container_cli_hide(ys, gt) == 0)
|
||||
cprintf(cbpre, "%s ", xml_name(x));
|
||||
|
||||
if (yang_keyword_get(ys) == Y_LIST){
|
||||
/* If list then first loop through keys */
|
||||
|
|
@ -358,7 +363,7 @@ xml2cvec(cxobj *xt,
|
|||
}
|
||||
}
|
||||
}
|
||||
if (debug > 1){
|
||||
if (clicon_debug_get() > 1){
|
||||
clicon_debug(2, "%s cvv:\n", __FUNCTION__);
|
||||
cvec_print(stderr, cvv);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 *
|
||||
|
|
|
|||
18
test/lib.sh
18
test/lib.sh
|
|
@ -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=$?
|
||||
|
|
|
|||
|
|
@ -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 "^$"
|
||||
|
|
|
|||
|
|
@ -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
204
test/test_cli_gen.sh
Executable 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
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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){
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue