* Pushed tag to 4.0.1.PRE
* Restconf RFC 8040 increased feature compliance
* Cache-Control: no-cache added in HTTP responses (RFC Section 5.5)
* Restconf monitoring capabilities (RFC Section 9.1)
* Added support for Yang extensions
* New plugin callback: ca_extension
* Main backend example includes example code on how to implement a Yang extension in a plugin.
* JSON changes
* Non-pretty-print output removed all extra spaces.
* Example: `{"nacm-example:x": 42}` --> {"nacm-example:x":42}`
* Empty JSON container changed from `null` to `{}`.
* Empty list and leafs remain as `null`
* Removed unnecessary configure dependencies
* libnsl, libcrypt, libm, if_vlan,...
* pseudo-plugin added, to enable callbacks also for main programs. Useful for extensions
* Yang Unique statements with multiple schema identifiers did not work on some platforms due to memory error.
This commit is contained in:
parent
fe46a0e093
commit
e7b60619da
60 changed files with 1619 additions and 568 deletions
27
CHANGELOG.md
27
CHANGELOG.md
|
|
@ -1,6 +1,31 @@
|
|||
# Clixon Changelog
|
||||
|
||||
## 4.0.0 (Expected: 13 July 2019)
|
||||
## 4.1.0 (Expected: August 2019)
|
||||
|
||||
### Major New features
|
||||
* Restconf RFC 8040 increased feature compliance
|
||||
* Cache-Control: no-cache added in HTTP responses (RFC Section 5.5)
|
||||
* Restconf monitoring capabilities (RFC Section 9.1)
|
||||
* Added support for Yang extensions
|
||||
* New plugin callback: ca_extension
|
||||
* Main backend example includes example code on how to implement a Yang extension in a plugin.
|
||||
|
||||
### API changes on existing features (you may need to change your code)
|
||||
* JSON changes
|
||||
* Non-pretty-print output removed all extra spaces.
|
||||
* Example: `{"nacm-example:x": 42}` --> {"nacm-example:x":42}`
|
||||
* Empty JSON container changed from `null` to `{}`.
|
||||
* Empty list and leafs remain as `null`
|
||||
|
||||
### Minor changes
|
||||
* Removed unnecessary configure dependencies
|
||||
* libnsl, libcrypt, libm, if_vlan,...
|
||||
* pseudo-plugin added, to enable callbacks also for main programs. Useful for extensions
|
||||
|
||||
### Corrected Bugs
|
||||
* Yang Unique statements with multiple schema identifiers did not work on some platforms due to memory error.
|
||||
|
||||
## 4.0.0 (13 July 2019)
|
||||
|
||||
### Summary
|
||||
|
||||
|
|
|
|||
17
README.md
17
README.md
|
|
@ -102,7 +102,6 @@ However, the following YANG syntax modules are not implemented (reference to RFC
|
|||
- require-instance
|
||||
- instance-identifier type
|
||||
- status (7.21.2)
|
||||
- extension (7.19) supported syntactically, but no hooks/plugins for extenstions
|
||||
- YIN (13)
|
||||
- Yang extended Xpath functions: re-match(), deref)(), derived-from(), derived-from-or-self(), enum-value(), bit-is-set() (10.2-10.6)
|
||||
- Default values on leaf-lists are not supported (7.7.2)
|
||||
|
|
@ -204,7 +203,6 @@ You can create namespace in three ways:
|
|||
* `xml_nsctx_node()` by copying an XML namespace context from an existing XML node.
|
||||
* `xml_nsctx_yang()` by computing an XML namespace context a yang module import statements.
|
||||
|
||||
|
||||
## Netconf
|
||||
|
||||
Clixon implements the following NETCONF proposals or standards:
|
||||
|
|
@ -308,17 +306,16 @@ The figure shows the SDK runtime of Clixon.
|
|||
|
||||
## Standard Compliance
|
||||
|
||||
This is work-in-progress on which standards Clixon supports:
|
||||
- [RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) YANG - A Data Modeling Language for the Network Configuration Protocol (NETCONF)
|
||||
Standards Clixon partially supports:
|
||||
- [RFC5277](http://www.rfc-base.org/txt/rfc-5277.txt) NETCONF Event Notifications
|
||||
- [RFC6020](https://www.rfc-editor.org/rfc/rfc6020.txt) YANG - A Data Modeling Language for the Network Configuration Protocol (NETCONF)
|
||||
- [RFC6241](http://www.rfc-base.org/txt/rfc-6241.txt) NETCONF Configuration Protocol
|
||||
- [RFC6242](http://www.rfc-base.org/txt/rfc-6242.txt) Using the NETCONF Configuration Protocol over Secure Shell (SSH)
|
||||
- [RFC7895](http://www.rfc-base.org/txt/rfc-7895.txt) YANG Module Library
|
||||
* [RFC7950](http://www.rfc-base.org/txt/rfc-7950.txt) The YANG 1.1 Data Modeling Language
|
||||
* [RFC7951](http://www.rfc-base.org/txt/rfc-7951.txt) JSON Encoding of Data Modeled with YANG
|
||||
- [RFC 6241: NETCONF Configuration Protocol](http://www.rfc-base.org/txt/rfc-6241.txt)
|
||||
- [RFC 6242: Using the NETCONF Configuration Protocol over Secure Shell (SSH)](http://www.rfc-base.org/txt/rfc-6242.txt)
|
||||
- [RFC 5277: NETCONF Event Notifications](http://www.rfc-base.org/txt/rfc-5277.txt)
|
||||
- [RFC 8341: Network Configuration Access Control Model](http://www.rfc-base.org/txt/rfc-8341.txt)
|
||||
- [RFC 8040: RESTCONF Protocol](https://tools.ietf.org/html/rfc8040).
|
||||
- [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341).
|
||||
- [RFC8040](https://tools.ietf.org/html/rfc8040) RESTCONF Protocol
|
||||
- [RFC8341](http://www.rfc-base.org/txt/rfc-8341.txt) Network Configuration Access Control Model
|
||||
- [XML 1.0](https://www.w3.org/TR/2008/REC-xml-20081126)
|
||||
- [Namespaces in XML 1.0](https://www.w3.org/TR/2009/REC-xml-names-20091208)
|
||||
- [XPATH 1.0](https://www.w3.org/TR/xpath-10)
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ CLIXON_LIB = libclixon$(SH_SUFFIX).$(CLIXON_MAJOR).$(CLIXON_MINOR)
|
|||
# even though it may exist in $(libdir). But the new version may not have been installed yet.
|
||||
LIBDEPS = $(top_srcdir)/lib/src/$(CLIXON_LIB)
|
||||
|
||||
LIBS = -L$(top_srcdir)/lib/src @LIBS@ $(top_srcdir)/lib/src/$(CLIXON_LIB) -lpthread
|
||||
LIBS = -L$(top_srcdir)/lib/src @LIBS@ $(top_srcdir)/lib/src/$(CLIXON_LIB)
|
||||
CPPFLAGS = @CPPFLAGS@ -fPIC
|
||||
INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@
|
||||
|
||||
|
|
|
|||
|
|
@ -149,6 +149,37 @@ backend_client_rm(clicon_handle h,
|
|||
return backend_client_delete(h, ce); /* actually purge it */
|
||||
}
|
||||
|
||||
/*!
|
||||
* Maybe should be in the restconf client instead of backend?
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] yspec Yang spec
|
||||
* @param[in] xpath Xpath selection, not used but may be to filter early
|
||||
* @param[out] xrs XML restconf-state node
|
||||
* @see netconf_create_hello
|
||||
* @see rfc8040 Sections 9.1
|
||||
*/
|
||||
static int
|
||||
client_get_capabilities(clicon_handle h,
|
||||
yang_stmt *yspec,
|
||||
char *xpath,
|
||||
cxobj **xret)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xrstate = NULL; /* xml restconf-state node */
|
||||
cxobj *xcap = NULL; /* xml capabilities node */
|
||||
|
||||
if ((xrstate = xpath_first(*xret, "restconf-state")) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "restconf-state not found in config node");
|
||||
goto done;
|
||||
}
|
||||
if ((xcap = xml_new("capabilities", xrstate, yspec)) == NULL)
|
||||
goto done;
|
||||
if (xml_parse_va(&xcap, yspec, "<capability>urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit</capability>") < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Get streams state according to RFC 8040 or RFC5277 common function
|
||||
* @param[in] h Clicon handle
|
||||
|
|
@ -165,23 +196,18 @@ static int
|
|||
client_get_streams(clicon_handle h,
|
||||
yang_stmt *yspec,
|
||||
char *xpath,
|
||||
char *module,
|
||||
yang_stmt *ymod,
|
||||
char *top,
|
||||
cxobj **xret)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *ystream = NULL; /* yang stream module */
|
||||
yang_stmt *yns = NULL; /* yang namespace */
|
||||
cxobj *x = NULL;
|
||||
cbuf *cb = NULL;
|
||||
int ret;
|
||||
|
||||
if ((ystream = yang_find(yspec, Y_MODULE, module)) == NULL){
|
||||
clicon_err(OE_YANG, 0, "%s yang module not found", module);
|
||||
goto done;
|
||||
}
|
||||
if ((yns = yang_find(ystream, Y_NAMESPACE, NULL)) == NULL){
|
||||
clicon_err(OE_YANG, 0, "%s yang namespace not found", module);
|
||||
if ((yns = yang_find(ymod, Y_NAMESPACE, NULL)) == NULL){
|
||||
clicon_err(OE_YANG, 0, "%s yang namespace not found", yang_argument_get(ymod));
|
||||
goto done;
|
||||
}
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
|
|
@ -189,6 +215,9 @@ client_get_streams(clicon_handle h,
|
|||
goto done;
|
||||
}
|
||||
cprintf(cb,"<%s xmlns=\"%s\">", top, yang_argument_get(yns));
|
||||
/* Second argument is a hack to have the same function for the
|
||||
* RFC5277 and 8040 stream cases
|
||||
*/
|
||||
if (stream_get_xml(h, strcmp(top,"restconf-state")==0, cb) < 0)
|
||||
goto done;
|
||||
cprintf(cb,"</%s>", top);
|
||||
|
|
@ -234,23 +263,47 @@ client_statedata(clicon_handle h,
|
|||
size_t xlen;
|
||||
int i;
|
||||
yang_stmt *yspec;
|
||||
yang_stmt *ymod;
|
||||
int ret;
|
||||
char *namespace;
|
||||
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
||||
goto done;
|
||||
}
|
||||
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277")){
|
||||
if ((ret = client_get_streams(h, yspec, xpath, "clixon-rfc5277", "netconf", xret)) < 0)
|
||||
if ((ymod = yang_find_module_by_name(yspec, "clixon-rfc5277")) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "yang module clixon-rfc5277 not found");
|
||||
goto done;
|
||||
}
|
||||
if ((namespace = yang_find_mynamespace(ymod)) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "clixon-rfc5277 namespace not found");
|
||||
goto done;
|
||||
}
|
||||
if (xml_parse_va(xret, yspec, "<netconf xmlns=\"%s\"/>", namespace) < 0)
|
||||
goto done;
|
||||
if ((ret = client_get_streams(h, yspec, xpath, ymod, "netconf", xret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
}
|
||||
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040")){
|
||||
if ((ret = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) < 0)
|
||||
if ((ymod = yang_find_module_by_name(yspec, "ietf-restconf-monitoring")) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "yang module ietf-restconf-monitoring not found");
|
||||
goto done;
|
||||
}
|
||||
if ((namespace = yang_find_mynamespace(ymod)) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "ietf-restconf-monitoring namespace not found");
|
||||
goto done;
|
||||
}
|
||||
if (xml_parse_va(xret, yspec, "<restconf-state xmlns=\"%s\"/>", namespace) < 0)
|
||||
goto done;
|
||||
if ((ret = client_get_streams(h, yspec, xpath, ymod, "restconf-state", xret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
if ((ret = client_get_capabilities(h, yspec, xpath, xret)) < 0)
|
||||
goto done;
|
||||
}
|
||||
if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895")){
|
||||
if ((ret = yang_modules_state_get(h, yspec, xpath, nsc, 0, xret)) < 0)
|
||||
|
|
|
|||
|
|
@ -332,6 +332,7 @@ main(int argc,
|
|||
cbuf *cbret = NULL; /* startup cbuf if invalid */
|
||||
enum startup_status status = STARTUP_ERR; /* Startup status */
|
||||
int ret;
|
||||
char *dir;
|
||||
|
||||
/* In the startup, logs to stderr & syslog and debug flag set later */
|
||||
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
|
||||
|
|
@ -573,6 +574,13 @@ main(int argc,
|
|||
if ((yspec = yspec_new()) == NULL)
|
||||
goto done;
|
||||
clicon_dbspec_yang_set(h, yspec);
|
||||
|
||||
/* Load backend plugins before yangs are loaded (eg extension callbacks) */
|
||||
if ((dir = clicon_backend_dir(h)) != NULL &&
|
||||
clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir,
|
||||
clicon_option_str(h, "CLICON_BACKEND_REGEXP")) < 0)
|
||||
goto done;
|
||||
|
||||
/* Load Yang modules
|
||||
* 1. Load a yang module as a specific absolute filename */
|
||||
if ((str = clicon_yang_main_file(h)) != NULL)
|
||||
|
|
@ -596,6 +604,9 @@ main(int argc,
|
|||
/* Add netconf yang spec, used by netconf client and as internal protocol */
|
||||
if (netconf_module_load(h) < 0)
|
||||
goto done;
|
||||
/* Load yang restconf module */
|
||||
if (yang_spec_parse_module(h, "ietf-restconf", NULL, yspec)< 0)
|
||||
goto done;
|
||||
/* Load yang Restconf stream discovery */
|
||||
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") &&
|
||||
yang_spec_parse_module(h, "ietf-restconf-monitoring", NULL, yspec)< 0)
|
||||
|
|
@ -646,8 +657,6 @@ main(int argc,
|
|||
goto done;
|
||||
case SM_NONE: /* Fall through *
|
||||
* Load plugins and call plugin_init() */
|
||||
if (backend_plugin_initiate(h) != 0)
|
||||
goto done;
|
||||
status = STARTUP_OK;
|
||||
break;
|
||||
case SM_RUNNING: /* Use running as startup */
|
||||
|
|
@ -735,7 +744,6 @@ main(int argc,
|
|||
clicon_err(OE_DEMON, errno, "Setting signal");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Initialize server socket and save it to handle */
|
||||
if ((ss = backend_server_socket(h)) < 0)
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -63,23 +63,6 @@
|
|||
#include "backend_plugin.h"
|
||||
#include "backend_commit.h"
|
||||
|
||||
/*! Load a plugin group.
|
||||
* @param[in] h Clicon handle
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
backend_plugin_initiate(clicon_handle h)
|
||||
{
|
||||
char *dir;
|
||||
|
||||
/* Load application plugins */
|
||||
if ((dir = clicon_backend_dir(h)) == NULL)
|
||||
return 0;
|
||||
return clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir,
|
||||
clicon_option_str(h, "CLICON_BACKEND_REGEXP"));
|
||||
}
|
||||
|
||||
/*! Request plugins to reset system state
|
||||
* The system 'state' should be the same as the contents of running_db
|
||||
* @param[in] h Clicon handle
|
||||
|
|
|
|||
|
|
@ -67,8 +67,6 @@ typedef struct {
|
|||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int backend_plugin_initiate(clicon_handle h);
|
||||
|
||||
int clixon_plugin_reset(clicon_handle h, char *db);
|
||||
|
||||
int clixon_plugin_statedata(clicon_handle h, yang_stmt *yspec, cvec *nsc,
|
||||
|
|
|
|||
|
|
@ -138,9 +138,6 @@ startup_mode_startup(clicon_handle h,
|
|||
clicon_err(OE_FATAL, 0, "Invalid startup db: %s", db);
|
||||
goto done;
|
||||
}
|
||||
/* Load plugins and call plugin_init() */
|
||||
if (backend_plugin_initiate(h) != 0)
|
||||
goto done;
|
||||
/* If startup does not exist, create it empty */
|
||||
if (xmldb_exists(h, db) != 1){ /* diff */
|
||||
if (xmldb_create(h, db) < 0) /* diff */
|
||||
|
|
|
|||
|
|
@ -47,9 +47,6 @@
|
|||
#include <ctype.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#ifdef HAVE_CRYPT_H
|
||||
#include <crypt.h>
|
||||
#endif
|
||||
#include <dirent.h>
|
||||
#include <syslog.h>
|
||||
#include <arpa/inet.h>
|
||||
|
|
|
|||
|
|
@ -284,6 +284,7 @@ main(int argc, char **argv)
|
|||
struct passwd *pw;
|
||||
char *str;
|
||||
int tabmode;
|
||||
char *dir;
|
||||
|
||||
/* Defaults */
|
||||
once = 0;
|
||||
|
|
@ -466,6 +467,11 @@ main(int argc, char **argv)
|
|||
*/
|
||||
cv_exclude_keys(clicon_cli_varonly(h));
|
||||
|
||||
/* Load cli plugins before yangs are loaded (eg extension callbacks) */
|
||||
if ((dir = clicon_cli_dir(h)) != NULL &&
|
||||
clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
|
||||
goto done;
|
||||
|
||||
/* Create top-level and store as option */
|
||||
if ((yspec = yspec_new()) == NULL)
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -319,7 +319,6 @@ int
|
|||
cli_syntax_load(clicon_handle h)
|
||||
{
|
||||
int retval = -1;
|
||||
char *plugin_dir = NULL;
|
||||
char *clispec_dir = NULL;
|
||||
char *clispec_file = NULL;
|
||||
int ndp;
|
||||
|
|
@ -336,7 +335,6 @@ cli_syntax_load(clicon_handle h)
|
|||
return 0;
|
||||
|
||||
/* Format plugin directory path */
|
||||
plugin_dir = clicon_cli_dir(h);
|
||||
clispec_dir = clicon_clispec_dir(h);
|
||||
clispec_file = clicon_option_str(h, "CLICON_CLISPEC_FILE");
|
||||
|
||||
|
|
@ -349,10 +347,6 @@ cli_syntax_load(clicon_handle h)
|
|||
|
||||
cli_syntax_set(h, stx);
|
||||
|
||||
/* Load cli plugins */
|
||||
if (plugin_dir &&
|
||||
clixon_plugins_load(h, CLIXON_PLUGIN_INIT, plugin_dir, NULL)< 0)
|
||||
goto done;
|
||||
if (clispec_file){
|
||||
if (cli_load_syntax(h, clispec_file, NULL) < 0)
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -48,9 +48,6 @@
|
|||
#include <ctype.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#ifdef HAVE_CRYPT_H
|
||||
#include <crypt.h>
|
||||
#endif
|
||||
#include <dirent.h>
|
||||
#include <syslog.h>
|
||||
#include <arpa/inet.h>
|
||||
|
|
|
|||
|
|
@ -488,6 +488,12 @@ main(int argc,
|
|||
if ((yspec = yspec_new()) == NULL)
|
||||
goto done;
|
||||
clicon_dbspec_yang_set(h, yspec);
|
||||
|
||||
/* Load netconf plugins before yangs are loaded (eg extension callbacks) */
|
||||
if ((dir = clicon_netconf_dir(h)) != NULL &&
|
||||
clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
|
||||
goto done;
|
||||
|
||||
/* Load Yang modules
|
||||
* 1. Load a yang module as a specific absolute filename */
|
||||
if ((str = clicon_yang_main_file(h)) != NULL){
|
||||
|
|
@ -514,15 +520,9 @@ main(int argc,
|
|||
/* Add netconf yang spec, used by netconf client and as internal protocol */
|
||||
if (netconf_module_load(h) < 0)
|
||||
goto done;
|
||||
/* Initialize plugins group */
|
||||
if ((dir = clicon_netconf_dir(h)) != NULL)
|
||||
if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
|
||||
goto done;
|
||||
|
||||
/* Call start function is all plugins before we go interactive */
|
||||
if (clixon_plugin_start(h) < 0)
|
||||
goto done;
|
||||
|
||||
if (!quiet)
|
||||
send_hello(h, 1);
|
||||
if (event_reg_fd(0, netconf_input_cb, h, "netconf socket") < 0)
|
||||
|
|
|
|||
|
|
@ -464,7 +464,7 @@ api_return_err(clicon_handle h,
|
|||
}
|
||||
else{
|
||||
FCGX_FPrintF(r->out, "{");
|
||||
FCGX_FPrintF(r->out, "\"ietf-restconf:errors\" : ");
|
||||
FCGX_FPrintF(r->out, "\"ietf-restconf:errors\":");
|
||||
FCGX_FPrintF(r->out, "%s", cbuf_get(cb));
|
||||
FCGX_FPrintF(r->out, "}\r\n");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,6 +190,7 @@ api_well_known(clicon_handle h,
|
|||
FCGX_Request *r)
|
||||
{
|
||||
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 */
|
||||
|
|
@ -210,24 +211,32 @@ static int
|
|||
api_root(clicon_handle h,
|
||||
FCGX_Request *r)
|
||||
{
|
||||
int retval = -1;
|
||||
char *media_accept;
|
||||
int use_xml = 0; /* By default use JSON */
|
||||
cxobj *xt = NULL;
|
||||
cbuf *cb = NULL;
|
||||
int pretty;
|
||||
int retval = -1;
|
||||
char *media_accept;
|
||||
int use_xml = 0; /* By default use JSON */
|
||||
cxobj *xt = NULL;
|
||||
cbuf *cb = NULL;
|
||||
int pretty;
|
||||
yang_stmt *yspec;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "No DB_SPEC");
|
||||
goto done;
|
||||
}
|
||||
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
|
||||
media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp);
|
||||
if (strcmp(media_accept, "application/yang-data+xml")==0)
|
||||
use_xml++;
|
||||
clicon_debug(1, "%s use-xml:%d media-accept:%s", __FUNCTION__, use_xml, media_accept);
|
||||
FCGX_SetExitStatus(200, r->out); /* OK */
|
||||
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
|
||||
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
|
||||
FCGX_FPrintF(r->out, "\r\n");
|
||||
|
||||
if (xml_parse_string("<restconf><data></data><operations></operations><yang-library-version>2016-06-21</yang-library-version></restconf>", NULL, &xt) < 0)
|
||||
if (xml_parse_string("<restconf xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><data/><operations/><yang-library-version>2016-06-21</yang-library-version></restconf>", NULL, &xt) < 0)
|
||||
goto done;
|
||||
if (xml_apply(xt, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
goto done;
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
clicon_err(OE_XML, errno, "cbuf_new");
|
||||
|
|
@ -274,6 +283,7 @@ api_yang_library_version(clicon_handle h,
|
|||
if (strcmp(media_accept, "application/yang-data+xml")==0)
|
||||
use_xml++;
|
||||
FCGX_SetExitStatus(200, r->out); /* OK */
|
||||
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
|
||||
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
|
||||
FCGX_FPrintF(r->out, "\r\n");
|
||||
if (xml_parse_va(&xt, NULL, "<yang-library-version>%s</yang-library-version>", ietf_yang_library_revision) < 0)
|
||||
|
|
@ -467,6 +477,41 @@ restconf_sig_term(int arg)
|
|||
exit(-1);
|
||||
}
|
||||
|
||||
/*! Callback for yang extensions ietf-restconf:yang-data
|
||||
* @see ietf-restconf.yang
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] yext Yang node of extension
|
||||
* @param[in] ys Yang node of (unknown) statement belonging to extension
|
||||
* @retval 0 OK, all callbacks executed OK
|
||||
* @retval -1 Error in one callback
|
||||
*/
|
||||
static int
|
||||
restconf_main_extension_cb(clicon_handle h,
|
||||
yang_stmt *yext,
|
||||
yang_stmt *ys)
|
||||
{
|
||||
int retval = -1;
|
||||
char *extname;
|
||||
char *modname;
|
||||
yang_stmt *ymod;
|
||||
yang_stmt *yc;
|
||||
|
||||
ymod = ys_module(yext);
|
||||
modname = yang_argument_get(ymod);
|
||||
extname = yang_argument_get(yext);
|
||||
if (strcmp(modname, "ietf-restconf") != 0 || strcmp(extname, "yang-data") != 0)
|
||||
goto ok;
|
||||
clicon_debug(1, "%s Enabled extension:%s:%s", __FUNCTION__, modname, extname);
|
||||
if ((yc = ys_prune(ys, 0)) == NULL)
|
||||
goto done;
|
||||
if (yn_insert(yang_parent_get(ys), yc) < 0)
|
||||
goto done;
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void
|
||||
restconf_sig_child(int arg)
|
||||
{
|
||||
|
|
@ -510,22 +555,23 @@ int
|
|||
main(int argc,
|
||||
char **argv)
|
||||
{
|
||||
int retval = -1;
|
||||
int sock;
|
||||
char *argv0 = argv[0];
|
||||
FCGX_Request request;
|
||||
FCGX_Request *r = &request;
|
||||
int c;
|
||||
char *sockpath;
|
||||
char *path;
|
||||
clicon_handle h;
|
||||
char *dir;
|
||||
int logdst = CLICON_LOG_SYSLOG;
|
||||
yang_stmt *yspec = NULL;
|
||||
yang_stmt *yspecfg = NULL; /* For config XXX clixon bug */
|
||||
char *stream_path;
|
||||
int finish;
|
||||
char *str;
|
||||
int retval = -1;
|
||||
int sock;
|
||||
char *argv0 = argv[0];
|
||||
FCGX_Request request;
|
||||
FCGX_Request *r = &request;
|
||||
int c;
|
||||
char *sockpath;
|
||||
char *path;
|
||||
clicon_handle h;
|
||||
char *dir;
|
||||
int logdst = CLICON_LOG_SYSLOG;
|
||||
yang_stmt *yspec = NULL;
|
||||
yang_stmt *yspecfg = NULL; /* For config XXX clixon bug */
|
||||
char *stream_path;
|
||||
int finish;
|
||||
char *str;
|
||||
clixon_plugin *cp = NULL;
|
||||
|
||||
/* In the startup, logs to stderr & debug flag set later */
|
||||
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
|
||||
|
|
@ -635,16 +681,22 @@ main(int argc,
|
|||
/* Access the remaining argv/argc options (after --) w clicon-argv_get() */
|
||||
clicon_argv_set(h, argv0, argc, argv);
|
||||
|
||||
/* Initialize plugins group */
|
||||
if ((dir = clicon_restconf_dir(h)) != NULL)
|
||||
if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
|
||||
return -1;
|
||||
|
||||
/* Create top-level yang spec and store as option */
|
||||
if ((yspec = yspec_new()) == NULL)
|
||||
goto done;
|
||||
clicon_dbspec_yang_set(h, yspec);
|
||||
|
||||
/* Load restconf plugins before yangs are loaded (eg extension callbacks) */
|
||||
if ((dir = clicon_restconf_dir(h)) != NULL)
|
||||
if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
|
||||
return -1;
|
||||
/* Create a pseudo-plugin to create extension callback to set the ietf-routing
|
||||
* yang-data extension for api-root top-level restconf function.
|
||||
*/
|
||||
if (clixon_pseudo_plugin(h, "pseudo restconf", &cp) < 0)
|
||||
goto done;
|
||||
cp->cp_api.ca_extension = restconf_main_extension_cb;
|
||||
|
||||
/* Load Yang modules
|
||||
* 1. Load a yang module as a specific absolute filename */
|
||||
if ((str = clicon_yang_main_file(h)) != NULL){
|
||||
|
|
@ -669,6 +721,10 @@ main(int argc,
|
|||
if (yang_modules_init(h) < 0)
|
||||
goto done;
|
||||
|
||||
/* Load yang restconf module */
|
||||
if (yang_spec_parse_module(h, "ietf-restconf", NULL, yspec)< 0)
|
||||
goto done;
|
||||
|
||||
/* Add netconf yang spec, used as internal protocol */
|
||||
if (netconf_module_load(h) < 0)
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -326,6 +326,7 @@ api_data_get2(clicon_handle h,
|
|||
}
|
||||
clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx));
|
||||
FCGX_SetExitStatus(200, r->out); /* OK */
|
||||
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
|
||||
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
|
||||
FCGX_FPrintF(r->out, "\r\n");
|
||||
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
|
||||
|
|
@ -663,7 +664,6 @@ api_data_post(clicon_handle h,
|
|||
clicon_log(LOG_WARNING, "%s: copy-config running->startup failed", __FUNCTION__);
|
||||
}
|
||||
}
|
||||
|
||||
FCGX_SetExitStatus(201, r->out); /* Created */
|
||||
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
|
||||
FCGX_FPrintF(r->out, "\r\n");
|
||||
|
|
|
|||
159
configure
vendored
159
configure
vendored
|
|
@ -2172,9 +2172,9 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
|
|||
: ${INSTALLFLAGS="-s"}
|
||||
|
||||
CLIXON_VERSION_MAJOR="4"
|
||||
CLIXON_VERSION_MINOR="0"
|
||||
CLIXON_VERSION_MINOR="1"
|
||||
CLIXON_VERSION_PATCH="0"
|
||||
CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\""
|
||||
CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}.PRE\""
|
||||
|
||||
# Check CLIgen
|
||||
if test "$prefix" = "NONE"; then
|
||||
|
|
@ -3645,45 +3645,6 @@ if test "$prefix" = "NONE"; then
|
|||
prefix=${ac_default_prefix}
|
||||
fi
|
||||
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -lm" >&5
|
||||
$as_echo_n "checking for main in -lm... " >&6; }
|
||||
if ${ac_cv_lib_m_main+:} false; then :
|
||||
$as_echo_n "(cached) " >&6
|
||||
else
|
||||
ac_check_lib_save_LIBS=$LIBS
|
||||
LIBS="-lm $LIBS"
|
||||
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
||||
/* end confdefs.h. */
|
||||
|
||||
|
||||
int
|
||||
main ()
|
||||
{
|
||||
return main ();
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
_ACEOF
|
||||
if ac_fn_c_try_link "$LINENO"; then :
|
||||
ac_cv_lib_m_main=yes
|
||||
else
|
||||
ac_cv_lib_m_main=no
|
||||
fi
|
||||
rm -f core conftest.err conftest.$ac_objext \
|
||||
conftest$ac_exeext conftest.$ac_ext
|
||||
LIBS=$ac_check_lib_save_LIBS
|
||||
fi
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_m_main" >&5
|
||||
$as_echo "$ac_cv_lib_m_main" >&6; }
|
||||
if test "x$ac_cv_lib_m_main" = xyes; then :
|
||||
cat >>confdefs.h <<_ACEOF
|
||||
#define HAVE_LIBM 1
|
||||
_ACEOF
|
||||
|
||||
LIBS="-lm $LIBS"
|
||||
|
||||
fi
|
||||
|
||||
SH_SUFFIX=".so"
|
||||
|
||||
# This is for cligen
|
||||
|
|
@ -4195,63 +4156,6 @@ if test "${with_configfile+set}" = set; then :
|
|||
fi
|
||||
|
||||
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for crypt in -lcrypt" >&5
|
||||
$as_echo_n "checking for crypt in -lcrypt... " >&6; }
|
||||
if ${ac_cv_lib_crypt_crypt+:} false; then :
|
||||
$as_echo_n "(cached) " >&6
|
||||
else
|
||||
ac_check_lib_save_LIBS=$LIBS
|
||||
LIBS="-lcrypt $LIBS"
|
||||
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
||||
/* end confdefs.h. */
|
||||
|
||||
/* Override any GCC internal prototype to avoid an error.
|
||||
Use char because int might match the return type of a GCC
|
||||
builtin and then its argument prototype would still apply. */
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
#endif
|
||||
char crypt ();
|
||||
int
|
||||
main ()
|
||||
{
|
||||
return crypt ();
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
_ACEOF
|
||||
if ac_fn_c_try_link "$LINENO"; then :
|
||||
ac_cv_lib_crypt_crypt=yes
|
||||
else
|
||||
ac_cv_lib_crypt_crypt=no
|
||||
fi
|
||||
rm -f core conftest.err conftest.$ac_objext \
|
||||
conftest$ac_exeext conftest.$ac_ext
|
||||
LIBS=$ac_check_lib_save_LIBS
|
||||
fi
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_crypt_crypt" >&5
|
||||
$as_echo "$ac_cv_lib_crypt_crypt" >&6; }
|
||||
if test "x$ac_cv_lib_crypt_crypt" = xyes; then :
|
||||
cat >>confdefs.h <<_ACEOF
|
||||
#define HAVE_LIBCRYPT 1
|
||||
_ACEOF
|
||||
|
||||
LIBS="-lcrypt $LIBS"
|
||||
|
||||
fi
|
||||
|
||||
for ac_header in crypt.h
|
||||
do :
|
||||
ac_fn_c_check_header_mongrel "$LINENO" "crypt.h" "ac_cv_header_crypt_h" "$ac_includes_default"
|
||||
if test "x$ac_cv_header_crypt_h" = xyes; then :
|
||||
cat >>confdefs.h <<_ACEOF
|
||||
#define HAVE_CRYPT_H 1
|
||||
_ACEOF
|
||||
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
|
||||
# user credentials for unix sockets
|
||||
for ac_header in sys/ucred.h
|
||||
|
|
@ -4269,20 +4173,6 @@ fi
|
|||
done
|
||||
|
||||
|
||||
# This is for Linux vlan code
|
||||
for ac_header in linux/if_vlan.h
|
||||
do :
|
||||
ac_fn_c_check_header_mongrel "$LINENO" "linux/if_vlan.h" "ac_cv_header_linux_if_vlan_h" "$ac_includes_default"
|
||||
if test "x$ac_cv_header_linux_if_vlan_h" = xyes; then :
|
||||
cat >>confdefs.h <<_ACEOF
|
||||
#define HAVE_LINUX_IF_VLAN_H 1
|
||||
_ACEOF
|
||||
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for socket in -lsocket" >&5
|
||||
$as_echo_n "checking for socket in -lsocket... " >&6; }
|
||||
if ${ac_cv_lib_socket_socket+:} false; then :
|
||||
|
|
@ -4328,51 +4218,6 @@ _ACEOF
|
|||
|
||||
fi
|
||||
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for xdr_char in -lnsl" >&5
|
||||
$as_echo_n "checking for xdr_char in -lnsl... " >&6; }
|
||||
if ${ac_cv_lib_nsl_xdr_char+:} false; then :
|
||||
$as_echo_n "(cached) " >&6
|
||||
else
|
||||
ac_check_lib_save_LIBS=$LIBS
|
||||
LIBS="-lnsl $LIBS"
|
||||
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
||||
/* end confdefs.h. */
|
||||
|
||||
/* Override any GCC internal prototype to avoid an error.
|
||||
Use char because int might match the return type of a GCC
|
||||
builtin and then its argument prototype would still apply. */
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
#endif
|
||||
char xdr_char ();
|
||||
int
|
||||
main ()
|
||||
{
|
||||
return xdr_char ();
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
_ACEOF
|
||||
if ac_fn_c_try_link "$LINENO"; then :
|
||||
ac_cv_lib_nsl_xdr_char=yes
|
||||
else
|
||||
ac_cv_lib_nsl_xdr_char=no
|
||||
fi
|
||||
rm -f core conftest.err conftest.$ac_objext \
|
||||
conftest$ac_exeext conftest.$ac_ext
|
||||
LIBS=$ac_check_lib_save_LIBS
|
||||
fi
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nsl_xdr_char" >&5
|
||||
$as_echo "$ac_cv_lib_nsl_xdr_char" >&6; }
|
||||
if test "x$ac_cv_lib_nsl_xdr_char" = xyes; then :
|
||||
cat >>confdefs.h <<_ACEOF
|
||||
#define HAVE_LIBNSL 1
|
||||
_ACEOF
|
||||
|
||||
LIBS="-lnsl $LIBS"
|
||||
|
||||
fi
|
||||
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5
|
||||
$as_echo_n "checking for dlopen in -ldl... " >&6; }
|
||||
if ${ac_cv_lib_dl_dlopen+:} false; then :
|
||||
|
|
|
|||
11
configure.ac
11
configure.ac
|
|
@ -43,9 +43,9 @@ AC_INIT(lib/clixon/clixon.h.in)
|
|||
: ${INSTALLFLAGS="-s"}
|
||||
|
||||
CLIXON_VERSION_MAJOR="4"
|
||||
CLIXON_VERSION_MINOR="0"
|
||||
CLIXON_VERSION_MINOR="1"
|
||||
CLIXON_VERSION_PATCH="0"
|
||||
CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\""
|
||||
CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}.PRE\""
|
||||
|
||||
# Check CLIgen
|
||||
if test "$prefix" = "NONE"; then
|
||||
|
|
@ -131,7 +131,6 @@ if test "$prefix" = "NONE"; then
|
|||
prefix=${ac_default_prefix}
|
||||
fi
|
||||
|
||||
AC_CHECK_LIB(m, main)
|
||||
SH_SUFFIX=".so"
|
||||
|
||||
# This is for cligen
|
||||
|
|
@ -202,19 +201,13 @@ AC_ARG_WITH([configfile],
|
|||
[AS_HELP_STRING([--with-configfile=FILE],[set default path to config file])],
|
||||
[CLIXON_DEFAULT_CONFIG="$withval"],)
|
||||
|
||||
AC_CHECK_LIB(crypt, crypt)
|
||||
AC_CHECK_HEADERS(crypt.h)
|
||||
|
||||
# user credentials for unix sockets
|
||||
AC_CHECK_HEADERS([sys/ucred.h],[],[],
|
||||
[[# include <sys/param.h>]]
|
||||
)
|
||||
|
||||
# This is for Linux vlan code
|
||||
AC_CHECK_HEADERS(linux/if_vlan.h)
|
||||
|
||||
AC_CHECK_LIB(socket, socket)
|
||||
AC_CHECK_LIB(nsl, xdr_char)
|
||||
AC_CHECK_LIB(dl, dlopen)
|
||||
|
||||
# This is for libxml2 XSD regex engine
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ General Public License Version 2.
|
|||
## Is Clixon extendible?
|
||||
Yes. All application semantics is defined in plugins with well-defined
|
||||
APIs. There are currently plugins for: CLI, Netconf, Restconf, the datastore and the backend.
|
||||
Clixon also supports Yang extensions, see main example.
|
||||
|
||||
## Which programming language is used?
|
||||
Clixon is written in C. The plugins are written in C. The CLI
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ all: $(PLUGINS)
|
|||
|
||||
CLISPECS = $(APPNAME)_cli.cli
|
||||
|
||||
YANGSPECS = clixon-example@2019-01-13.yang
|
||||
YANGSPECS = clixon-example@2019-07-23.yang
|
||||
|
||||
# Backend plugin
|
||||
BE_SRC = $(APPNAME)_backend.c
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
* [Streams](#streams)
|
||||
* [RPC Operations](#rpc-operations)
|
||||
* [State data](#state-data)
|
||||
* [Extensions](#extension)
|
||||
* [Authentication and NACM](#authentication-and-nacm)
|
||||
* [Systemd](#systemd)
|
||||
* [Docker](#docker)
|
||||
|
|
@ -270,6 +271,33 @@ The example contains some stubs for authorization according to [RFC8341(NACM)](h
|
|||
* A basic auth HTTP callback, see: example_restconf_credentials() containing three example users: andy, wilma, and guest, according to the examples in Appendix A in [RFC8341](https://tools.ietf.org/html/rfc8341).
|
||||
* A NACM backend plugin reporting the mandatory NACM state variables.
|
||||
|
||||
## Extensions
|
||||
|
||||
Clixon supports Yang extensions, but you need to write plugin code.
|
||||
The example backend implements an "example:e4" Yang extension, as follows:
|
||||
```
|
||||
extension e4 {
|
||||
description
|
||||
"The first child of the ex:e4 (unknown) statement is replaced with its first
|
||||
child. This means that 'uses bar;' in the ex:e4 statement below is a valid
|
||||
data node";
|
||||
argument arg;
|
||||
}
|
||||
ex:e4 arg1{
|
||||
uses bar;
|
||||
}
|
||||
```
|
||||
|
||||
The backend plugin code registers an extension callback in the init struct:
|
||||
```
|
||||
.ca_extension=example_extension, /* yang extensions */
|
||||
```
|
||||
|
||||
The callback then receives a callback on all "unknown" Yang statements
|
||||
during yang parsing. If the extension matches "example:e4", it applies
|
||||
the extension. In the example, it replaces the "ex:e4" statements with
|
||||
its first child, making it a proper yang statement.
|
||||
|
||||
## Systemd
|
||||
|
||||
Example systemd files for backend and restconf daemons are found under the systemd directory. Install them under /etc/systemd/system for example.
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@ module clixon-example {
|
|||
yang-version 1.1;
|
||||
namespace "urn:example:clixon";
|
||||
prefix ex;
|
||||
revision 2019-07-23 {
|
||||
description "Extension e4. Released in Clixon 4.1.0";
|
||||
}
|
||||
revision 2019-01-13 {
|
||||
description
|
||||
"Released in Clixon 3.9";
|
||||
description "Released in Clixon 3.9";
|
||||
}
|
||||
import ietf-interfaces {
|
||||
prefix if;
|
||||
|
|
@ -40,6 +42,23 @@ module clixon-example {
|
|||
type string;
|
||||
}
|
||||
}
|
||||
/* yang extension implemented by the example backend code. */
|
||||
extension e4 {
|
||||
description
|
||||
"The first child of the ex:e4 (unknown) statement is replaced with its first
|
||||
child. This means that 'uses bar;' in the ex:e4 statement below is a valid
|
||||
data node";
|
||||
argument arg;
|
||||
}
|
||||
grouping bar {
|
||||
leaf bar{
|
||||
type string;
|
||||
}
|
||||
}
|
||||
ex:e4 arg1{
|
||||
uses bar;
|
||||
}
|
||||
|
||||
/* Example notification as used in RFC 5277 and RFC 8040 */
|
||||
notification event {
|
||||
description "Example notification event.";
|
||||
|
|
@ -351,6 +351,42 @@ example_statedata(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Callback for yang extensions example:e4
|
||||
*
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] yext Yang node of extension
|
||||
* @param[in] ys Yang node of (unknown) statement belonging to extension
|
||||
* @retval 0 OK, all callbacks executed OK
|
||||
* @retval -1 Error in one callback
|
||||
*/
|
||||
int
|
||||
example_extension(clicon_handle h,
|
||||
yang_stmt *yext,
|
||||
yang_stmt *ys)
|
||||
{
|
||||
int retval = -1;
|
||||
char *extname;
|
||||
char *modname;
|
||||
yang_stmt *ymod;
|
||||
yang_stmt *yc;
|
||||
|
||||
ymod = ys_module(yext);
|
||||
modname = yang_argument_get(ymod);
|
||||
extname = yang_argument_get(yext);
|
||||
if (strcmp(modname, "example") != 0 || strcmp(extname, "e4") != 0)
|
||||
goto ok;
|
||||
clicon_debug(1, "%s Enabled extension:%s:%s", __FUNCTION__, modname, extname);
|
||||
if ((yc = ys_prune(ys, 0)) == NULL)
|
||||
goto done;
|
||||
if (yn_insert(yang_parent_get(ys), yc) < 0)
|
||||
goto done;
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/*! Testcase upgrade function moving interfaces-state to interfaces
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] xn XML tree to be updated
|
||||
|
|
@ -616,6 +652,7 @@ static clixon_plugin_api api = {
|
|||
clixon_plugin_init, /* init - must be called clixon_plugin_init */
|
||||
example_start, /* start */
|
||||
example_exit, /* exit */
|
||||
.ca_extension=example_extension, /* yang extensions */
|
||||
.ca_reset=example_reset, /* reset */
|
||||
.ca_statedata=example_statedata, /* statedata */
|
||||
.ca_trans_begin=main_begin, /* trans begin */
|
||||
|
|
|
|||
|
|
@ -24,9 +24,6 @@
|
|||
/* Define to 1 if you have the <cligen/cligen.h> header file. */
|
||||
#undef HAVE_CLIGEN_CLIGEN_H
|
||||
|
||||
/* Define to 1 if you have the <crypt.h> header file. */
|
||||
#undef HAVE_CRYPT_H
|
||||
|
||||
/* Define to 1 if you have the `inet_aton' function. */
|
||||
#undef HAVE_INET_ATON
|
||||
|
||||
|
|
@ -36,9 +33,6 @@
|
|||
/* Define to 1 if you have the `cligen' library (-lcligen). */
|
||||
#undef HAVE_LIBCLIGEN
|
||||
|
||||
/* Define to 1 if you have the `crypt' library (-lcrypt). */
|
||||
#undef HAVE_LIBCRYPT
|
||||
|
||||
/* Define to 1 if you have the `curl' library (-lcurl). */
|
||||
#undef HAVE_LIBCURL
|
||||
|
||||
|
|
@ -48,21 +42,12 @@
|
|||
/* Define to 1 if you have the `fcgi' library (-lfcgi). */
|
||||
#undef HAVE_LIBFCGI
|
||||
|
||||
/* Define to 1 if you have the `m' library (-lm). */
|
||||
#undef HAVE_LIBM
|
||||
|
||||
/* Define to 1 if you have the `nsl' library (-lnsl). */
|
||||
#undef HAVE_LIBNSL
|
||||
|
||||
/* Define to 1 if you have the `socket' library (-lsocket). */
|
||||
#undef HAVE_LIBSOCKET
|
||||
|
||||
/* Define to 1 if you have the `xml2' library (-lxml2). */
|
||||
#undef HAVE_LIBXML2
|
||||
|
||||
/* Define to 1 if you have the <linux/if_vlan.h> header file. */
|
||||
#undef HAVE_LINUX_IF_VLAN_H
|
||||
|
||||
/* Define to 1 if you have the <memory.h> header file. */
|
||||
#undef HAVE_MEMORY_H
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -32,6 +32,8 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
|
||||
* JSON support functions.
|
||||
* JSON syntax is according to:
|
||||
* http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
|
||||
*/
|
||||
#ifndef _CLIXON_JSON_H
|
||||
#define _CLIXON_JSON_H
|
||||
|
|
|
|||
|
|
@ -106,6 +106,20 @@ typedef int (plgstart_t)(clicon_handle); /* Plugin start */
|
|||
*/
|
||||
typedef int (plgexit_t)(clicon_handle); /* Plugin exit */
|
||||
|
||||
/* For yang extension handling.
|
||||
* Called at parsing of yang module containing a statement of an extension.
|
||||
* A plugin may identify the extension by its name, and perform actions
|
||||
* on the yang statement, such as transforming the yang.
|
||||
* A callback is made for every statement, which means that several calls per
|
||||
* extension can be made.
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] yext Yang node of extension
|
||||
* @param[in] ys Yang node of (unknown) statement belonging to extension
|
||||
* @retval 0 OK, all callbacks executed OK
|
||||
* @retval -1 Error in one callback
|
||||
*/
|
||||
typedef int (plgextension_t)(clicon_handle h, yang_stmt *yext, yang_stmt *ys);
|
||||
|
||||
/*! Called by restconf to check credentials and return username
|
||||
*/
|
||||
|
||||
|
|
@ -168,6 +182,7 @@ struct clixon_plugin_api{
|
|||
plginit2_t *ca_init; /* Clixon plugin Init (implicit) */
|
||||
plgstart_t *ca_start; /* Plugin start */
|
||||
plgexit_t *ca_exit; /* Plugin exit */
|
||||
plgextension_t *ca_extension; /* Yang extension handler */
|
||||
union {
|
||||
struct { /* cli-specific */
|
||||
cli_prompthook_t *ci_prompt; /* Prompt hook */
|
||||
|
|
@ -189,7 +204,6 @@ struct clixon_plugin_api{
|
|||
trans_cb_t *cb_trans_revert; /* Transaction revert */
|
||||
trans_cb_t *cb_trans_end; /* Transaction completed */
|
||||
trans_cb_t *cb_trans_abort; /* Transaction aborted */
|
||||
|
||||
} cau_backend;
|
||||
|
||||
} u;
|
||||
|
|
@ -245,12 +259,16 @@ clixon_plugin *clixon_plugin_find(clicon_handle h, char *name);
|
|||
|
||||
int clixon_plugins_load(clicon_handle h, char *function, char *dir, char *regexp);
|
||||
|
||||
int clixon_pseudo_plugin(clicon_handle h, char *name, clixon_plugin **cpp);
|
||||
|
||||
int clixon_plugin_start(clicon_handle h);
|
||||
|
||||
int clixon_plugin_exit(clicon_handle h);
|
||||
|
||||
int clixon_plugin_auth(clicon_handle h, void *arg);
|
||||
|
||||
int clixon_plugin_extension(clicon_handle h, yang_stmt *yext, yang_stmt *ys);
|
||||
|
||||
/* rpc callback API */
|
||||
int rpc_callback_register(clicon_handle h, clicon_rpc_cb cb, void *arg, char *namespace, char *name);
|
||||
int rpc_callback_delete_all(clicon_handle h);
|
||||
|
|
|
|||
|
|
@ -140,7 +140,6 @@ struct xml;
|
|||
|
||||
typedef struct yang_stmt yang_stmt; /* Defined in clixon_yang_internal */
|
||||
|
||||
|
||||
typedef int (yang_applyfn_t)(yang_stmt *ys, void *arg);
|
||||
|
||||
/*
|
||||
|
|
@ -157,6 +156,8 @@ int yang_cvec_set(yang_stmt *ys, cvec *cvv);
|
|||
/* Other functions */
|
||||
yang_stmt *yspec_new(void);
|
||||
yang_stmt *ys_new(enum rfc_6020 keyw);
|
||||
yang_stmt *ys_prune(yang_stmt *yp, int i);
|
||||
|
||||
int ys_free(yang_stmt *ys);
|
||||
int yspec_free(yang_stmt *yspec);
|
||||
int ys_cp(yang_stmt *new, yang_stmt *old);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -91,14 +91,17 @@ enum array_element_type{
|
|||
};
|
||||
|
||||
enum childtype{
|
||||
NULL_CHILD=0, /* eg <a/> no children */
|
||||
NULL_CHILD=0, /* eg <a/> no children. Translated to null if in
|
||||
* array or leaf terminal, and to {} if proper object, ie container.
|
||||
* anyxml/anydata?
|
||||
*/
|
||||
BODY_CHILD, /* eg one child which is a body, eg <a>1</a> */
|
||||
ANY_CHILD, /* eg <a><b/></a> or <a><b/><c/></a> */
|
||||
};
|
||||
|
||||
/*! x is element and has exactly one child which in turn has none
|
||||
* remove attributes from x
|
||||
* Clone from clixon_xml_map.c
|
||||
* @see tleaf in clixon_xml_map.c
|
||||
*/
|
||||
static enum childtype
|
||||
child_type(cxobj *x)
|
||||
|
|
@ -372,6 +375,7 @@ xml2json1_cbuf(cbuf *cb,
|
|||
modname0 = modname; /* modname0 is ancestor ns passed to child */
|
||||
}
|
||||
childt = child_type(x);
|
||||
|
||||
if (pretty==2)
|
||||
cprintf(cb, "#%s_array, %s_child ",
|
||||
arraytype2str(arraytype),
|
||||
|
|
@ -393,11 +397,17 @@ xml2json1_cbuf(cbuf *cb,
|
|||
cprintf(cb, "%*s\"", pretty?(level*JSON_INDENT):0, "");
|
||||
if (modname)
|
||||
cprintf(cb, "%s:", modname);
|
||||
cprintf(cb, "%s\": ", xml_name(x));
|
||||
cprintf(cb, "%s\":%s", xml_name(x), pretty?" ":"");
|
||||
}
|
||||
switch (childt){
|
||||
case NULL_CHILD:
|
||||
cprintf(cb, "null");
|
||||
/* If x is a container, use {} instead of null
|
||||
* That is, x is not a list or leaf
|
||||
*/
|
||||
if (ys && yang_keyword_get(ys) == Y_CONTAINER)
|
||||
cprintf(cb, "{}");
|
||||
else
|
||||
cprintf(cb, "null");
|
||||
break;
|
||||
case BODY_CHILD:
|
||||
break;
|
||||
|
|
@ -413,7 +423,7 @@ xml2json1_cbuf(cbuf *cb,
|
|||
cprintf(cb, "%*s\"", pretty?(level*JSON_INDENT):0, "");
|
||||
if (modname)
|
||||
cprintf(cb, "%s:", modname);
|
||||
cprintf(cb, "%s\": ", xml_name(x));
|
||||
cprintf(cb, "%s\":%s", xml_name(x), pretty?" ":"");
|
||||
level++;
|
||||
cprintf(cb, "[%s%*s",
|
||||
pretty?"\n":"",
|
||||
|
|
@ -443,7 +453,7 @@ xml2json1_cbuf(cbuf *cb,
|
|||
case BODY_CHILD:
|
||||
break;
|
||||
case ANY_CHILD:
|
||||
cprintf(cb, "{ %s", pretty?"\n":"");
|
||||
cprintf(cb, "{%s", pretty?"\n":"");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -169,14 +169,14 @@ clixon_plugin_find(clicon_handle h,
|
|||
}
|
||||
|
||||
/*! Load a dynamic plugin object and call its init-function
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] file Which plugin to load
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] file Which plugin to load
|
||||
* @param[in] function Which function symbol to load and call
|
||||
* @param[in] dlflags See man(3) dlopen
|
||||
* @param[in] dlflags See man(3) dlopen
|
||||
* @param[out] cpp Clixon plugin structure (if retval is 1)
|
||||
* @retval 1 OK
|
||||
* @retval 0 Failed load, log, skip and continue with other plugins
|
||||
* @retval -1 Error
|
||||
* @retval 1 OK
|
||||
* @retval 0 Failed load, log, skip and continue with other plugins
|
||||
* @retval -1 Error
|
||||
* @see clixon_plugins_load Load all plugins
|
||||
*/
|
||||
static int
|
||||
|
|
@ -236,8 +236,6 @@ plugin_load_one(clicon_handle h,
|
|||
if ((p=strrchr(name, '.')) != NULL)
|
||||
*p = '\0';
|
||||
/* Copy name to struct */
|
||||
memcpy(cp->cp_name, name, strlen(name)+1);
|
||||
|
||||
snprintf(cp->cp_name, sizeof(cp->cp_name), "%*s",
|
||||
(int)strlen(name), name);
|
||||
cp->cp_api = *api;
|
||||
|
|
@ -274,7 +272,7 @@ clixon_plugins_load(clicon_handle h,
|
|||
struct dirent *dp = NULL;
|
||||
int i;
|
||||
char filename[MAXPATHLEN];
|
||||
clixon_plugin *cp;
|
||||
clixon_plugin *cp = NULL;
|
||||
int ret;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
|
|
@ -305,6 +303,47 @@ done:
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Create a pseudo plugin so that a main function can register callbacks
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] name Plugin name
|
||||
* @param[out] cpp Clixon plugin structure (direct pointer)
|
||||
* @retval 0 OK, with cpp set
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
clixon_pseudo_plugin(clicon_handle h,
|
||||
char *name,
|
||||
clixon_plugin **cpp)
|
||||
{
|
||||
int retval = -1;
|
||||
clixon_plugin *cp = NULL;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
|
||||
/* Create a pseudo plugins */
|
||||
/* Note: sizeof clixon_plugin_api which is largest of clixon_plugin_api:s */
|
||||
if ((cp = (clixon_plugin *)malloc(sizeof(struct clixon_plugin))) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "malloc");
|
||||
goto done;
|
||||
}
|
||||
memset(cp, 0, sizeof(struct clixon_plugin));
|
||||
snprintf(cp->cp_name, sizeof(cp->cp_name), "%*s", (int)strlen(name), name);
|
||||
|
||||
_clixon_nplugins++;
|
||||
if ((_clixon_plugins = realloc(_clixon_plugins, _clixon_nplugins*sizeof(clixon_plugin))) == NULL) {
|
||||
clicon_err(OE_UNIX, errno, "realloc");
|
||||
goto done;
|
||||
}
|
||||
_clixon_plugins[_clixon_nplugins-1] = *cp;
|
||||
*cpp = &_clixon_plugins[_clixon_nplugins-1];
|
||||
|
||||
retval = 0;
|
||||
done:
|
||||
if (cp)
|
||||
free(cp);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Call plugin_start in all plugins
|
||||
* @param[in] h Clicon handle
|
||||
* Call plugin start functions (if defined)
|
||||
|
|
@ -395,11 +434,45 @@ clixon_plugin_auth(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Callback for a yang extension (unknown) statement
|
||||
* Called at parsing of yang module containing a statement of an extension.
|
||||
* A plugin may identify the extension and perform actions
|
||||
* on the yang statement, such as transforming the yang.
|
||||
* A callback is made for every statement, which means that several calls per
|
||||
* extension can be made.
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] yext Yang node of extension
|
||||
* @param[in] ys Yang node of (unknown) statement belonging to extension
|
||||
* @retval 0 OK, all callbacks executed OK
|
||||
* @retval -1 Error in one callback
|
||||
*/
|
||||
int
|
||||
clixon_plugin_extension(clicon_handle h,
|
||||
yang_stmt *yext,
|
||||
yang_stmt *ys)
|
||||
{
|
||||
clixon_plugin *cp;
|
||||
int i;
|
||||
plgextension_t *extfn; /* Plugin extension fn */
|
||||
int retval = 1;
|
||||
|
||||
for (i = 0; i < _clixon_nplugins; i++) {
|
||||
cp = &_clixon_plugins[i];
|
||||
if ((extfn = cp->cp_api.ca_extension) == NULL)
|
||||
continue;
|
||||
if ((retval = extfn(h, yext, ys)) < 0) {
|
||||
clicon_debug(1, "plugin_extension() failed");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------
|
||||
* RPC callbacks for both client/frontend and backend plugins.
|
||||
* RPC callbacks are explicitly registered in the plugin_init() function
|
||||
* with a tag and a function
|
||||
* WHen the the tag is encountered, the callback is called.
|
||||
* When the the tag is encountered, the callback is called.
|
||||
* Primarily backend, but also netconf and restconf frontend plugins.
|
||||
* CLI frontend so far have direct callbacks, ie functions in the cligen
|
||||
* specification are directly dlsym:ed to the CLI plugin.
|
||||
|
|
@ -511,11 +584,11 @@ rpc_callback_call(clicon_handle h,
|
|||
|
||||
if (rpc_cb_list == NULL)
|
||||
return 0;
|
||||
name = xml_name(xe);
|
||||
prefix = xml_prefix(xe);
|
||||
xml2ns(xe, prefix, &namespace);
|
||||
rc = rpc_cb_list;
|
||||
do {
|
||||
name = xml_name(xe);
|
||||
prefix = xml_prefix(xe);
|
||||
xml2ns(xe, prefix, &namespace);
|
||||
if (strcmp(rc->rc_name, name) == 0 &&
|
||||
namespace && rc->rc_namespace &&
|
||||
strcmp(rc->rc_namespace, namespace) == 0){
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
* if ((vec = clicon_strsep("/home/user/src/clixon", "/", &nvec)) == NULL)
|
||||
* err;
|
||||
* for (i=0; i<nvec; i++){
|
||||
* v = vec[i++];
|
||||
* v = vec[i];
|
||||
* ...
|
||||
* }
|
||||
* free(vec);
|
||||
|
|
|
|||
|
|
@ -110,7 +110,9 @@ isxmlns(cxobj *x)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*! x is element and has eactly one child which in turn has none */
|
||||
/*! x is element and has eactly one child which in turn has none
|
||||
* @see child_type in clixon_json.c
|
||||
*/
|
||||
static int
|
||||
tleaf(cxobj *x)
|
||||
{
|
||||
|
|
@ -2420,7 +2422,7 @@ api_path2xpath_cvv(cvec *api_path,
|
|||
nodeid = cv_name_get(cv);
|
||||
if (nodeid_split(nodeid, &prefix, &name) < 0)
|
||||
goto done;
|
||||
clicon_debug(1, "%s [%d] cvname:%s", __FUNCTION__, i, name);
|
||||
clicon_debug(1, "%s [%d] cvname: %s:%s", __FUNCTION__, i, prefix?prefix:"", name);
|
||||
if (i == offset){ /* top-node */
|
||||
if (prefix == NULL){
|
||||
clicon_err(OE_XML, EINVAL, "'%s': Expected prefix:name", nodeid);
|
||||
|
|
|
|||
|
|
@ -289,8 +289,6 @@ ys_free1(yang_stmt *ys)
|
|||
{
|
||||
if (ys->ys_argument)
|
||||
free(ys->ys_argument);
|
||||
if (ys->ys_extra)
|
||||
free(ys->ys_extra);
|
||||
if (ys->ys_cv)
|
||||
cv_free(ys->ys_cv);
|
||||
if (ys->ys_cvec)
|
||||
|
|
@ -309,7 +307,7 @@ ys_free1(yang_stmt *ys)
|
|||
* @see ys_free Deallocate yang node
|
||||
* @note Do not call this in a loop of yang children (unless you know what you are doing)
|
||||
*/
|
||||
static yang_stmt *
|
||||
yang_stmt *
|
||||
ys_prune(yang_stmt *yp,
|
||||
int i)
|
||||
{
|
||||
|
|
@ -323,7 +321,7 @@ ys_prune(yang_stmt *yp,
|
|||
memmove(&yp->ys_stmt[i],
|
||||
&yp->ys_stmt[i+1],
|
||||
size);
|
||||
yc = yp->ys_stmt[yp->ys_len--] = NULL;;
|
||||
yp->ys_stmt[yp->ys_len--] = NULL;;
|
||||
done:
|
||||
return yc;
|
||||
}
|
||||
|
|
@ -411,11 +409,6 @@ ys_cp(yang_stmt *ynew,
|
|||
clicon_err(OE_YANG, errno, "strdup");
|
||||
goto done;
|
||||
}
|
||||
if (yold->ys_extra)
|
||||
if ((ynew->ys_extra = strdup(yold->ys_extra)) == NULL){
|
||||
clicon_err(OE_YANG, errno, "strdup");
|
||||
goto done;
|
||||
}
|
||||
if (yold->ys_cv)
|
||||
if ((ynew->ys_cv = cv_dup(yold->ys_cv)) == NULL){
|
||||
clicon_err(OE_YANG, errno, "cv_dup");
|
||||
|
|
@ -473,6 +466,8 @@ ys_dup(yang_stmt *old)
|
|||
*
|
||||
* @param[in] ys_parent Add child to this parent
|
||||
* @param[in] ys_child Add this child
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* Also add parent to child as up-pointer
|
||||
*/
|
||||
int
|
||||
|
|
@ -646,7 +641,7 @@ yang_find_datanode(yang_stmt *yn,
|
|||
goto match;
|
||||
}
|
||||
} /* Y_CHOICE */
|
||||
else
|
||||
else{
|
||||
if (yang_datanode(ys)){
|
||||
if (argument == NULL)
|
||||
ysmatch = ys;
|
||||
|
|
@ -656,6 +651,7 @@ yang_find_datanode(yang_stmt *yn,
|
|||
if (ysmatch)
|
||||
goto match;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Special case: if not match and yang node is module or submodule, extend
|
||||
* search to include submodules */
|
||||
|
|
@ -1794,44 +1790,46 @@ ys_populate_unique(clicon_handle h,
|
|||
/*! Populate unknown node with extension
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] ys The yang statement (unknown) to populate.
|
||||
* RFC 7950 Sec 7.19:
|
||||
* If no "argument" statement is present, the keyword expects no argument when
|
||||
* it is used.
|
||||
*/
|
||||
static int
|
||||
ys_populate_unknown(clicon_handle h,
|
||||
yang_stmt *ys)
|
||||
{
|
||||
int retval = -1;
|
||||
int cvret;
|
||||
char *reason = NULL;
|
||||
int retval = -1;
|
||||
yang_stmt *ymod;
|
||||
yang_stmt *yext; /* extension */
|
||||
char *prefix = NULL;
|
||||
char *id = NULL;
|
||||
char *extra;
|
||||
char *argument; /* This is the unknown optional argument */
|
||||
cg_var *cv;
|
||||
|
||||
if ((extra = ys->ys_extra) == NULL)
|
||||
goto ok;
|
||||
/* Find extension, if found, store it as unknown, if not,
|
||||
break for error */
|
||||
if (nodeid_split(yang_argument_get(ys), &prefix, &id) < 0)
|
||||
goto done;
|
||||
if ((ymod = yang_find_module_by_prefix(ys, prefix)) == NULL)
|
||||
goto ok; /* shouldnt happen */
|
||||
if (yang_find(ymod, Y_EXTENSION, id) == NULL){
|
||||
clicon_err(OE_YANG, errno, "Extension %s:%s not found", prefix, id);
|
||||
if ((ymod = yang_find_module_by_prefix(ys, prefix)) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "Extension %s:%s, module not found", prefix, id);
|
||||
goto done;
|
||||
}
|
||||
if ((ys->ys_cv = cv_new(CGV_STRING)) == NULL){
|
||||
clicon_err(OE_YANG, errno, "cv_new");
|
||||
if ((yext = yang_find(ymod, Y_EXTENSION, id)) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "Extension %s:%s not found", prefix, id);
|
||||
goto done;
|
||||
}
|
||||
if ((cvret = cv_parse1(extra, ys->ys_cv, &reason)) < 0){ /* error */
|
||||
clicon_err(OE_YANG, errno, "parsing cv");
|
||||
goto done;
|
||||
/* Optional argument (only if "argument") - save it in ys_cv */
|
||||
if ((cv = yang_cv_get(ys)) != NULL &&
|
||||
(argument = cv_string_get(cv)) != NULL){
|
||||
if (yang_find(yext, Y_ARGUMENT, NULL) == NULL &&
|
||||
argument != NULL){
|
||||
clicon_err(OE_YANG, 0, "No argument specified in extension %s, but argument %s present when used", yang_argument_get(ys), argument);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
if (cvret == 0){ /* parsing failed */
|
||||
clicon_err(OE_YANG, errno, "Parsing CV: %s", reason);
|
||||
/* Make extension callbacks that may alter yang structure */
|
||||
if (clixon_plugin_extension(h, yext, ys) < 0)
|
||||
goto done;
|
||||
}
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
if (prefix)
|
||||
|
|
@ -2581,39 +2579,62 @@ yang_parse_recurse(clicon_handle h,
|
|||
return retval; /* top-level (sub)module */
|
||||
}
|
||||
|
||||
int
|
||||
static int
|
||||
ys_schemanode_check(yang_stmt *ys,
|
||||
void *arg)
|
||||
void *dummy)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *yspec;
|
||||
yang_stmt *yres;
|
||||
yang_stmt *yp;
|
||||
int retval = -1;
|
||||
yang_stmt *yspec;
|
||||
yang_stmt *yres = NULL;
|
||||
yang_stmt *yp;
|
||||
char *arg;
|
||||
enum rfc_6020 keyword;
|
||||
|
||||
yp = ys->ys_parent;
|
||||
switch (ys->ys_keyword){
|
||||
yp = yang_parent_get(ys);
|
||||
arg = yang_argument_get(ys);
|
||||
keyword = yang_keyword_get(ys);
|
||||
switch (yang_keyword_get(ys)){
|
||||
case Y_AUGMENT:
|
||||
if (yp->ys_keyword == Y_MODULE || /* Not top-level */
|
||||
yp->ys_keyword == Y_SUBMODULE)
|
||||
if (yang_keyword_get(yp) == Y_MODULE || /* Not top-level */
|
||||
yang_keyword_get(yp) == Y_SUBMODULE)
|
||||
break;
|
||||
/* fallthru */
|
||||
case Y_REFINE:
|
||||
case Y_UNIQUE:
|
||||
if (yang_desc_schema_nodeid(yp, ys->ys_argument, -1, &yres) < 0)
|
||||
if (yang_desc_schema_nodeid(yp, arg, -1, &yres) < 0)
|
||||
goto done;
|
||||
if (yres == NULL){
|
||||
clicon_err(OE_YANG, 0, "schemanode sanity check of %s %s",
|
||||
yang_key2str(ys->ys_keyword),
|
||||
ys->ys_argument);
|
||||
yang_key2str(keyword), arg);
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
case Y_UNIQUE:{
|
||||
char **vec = NULL;
|
||||
char *v;
|
||||
int nvec;
|
||||
int i;
|
||||
/* Unique: Sec 7.8.3 It takes as an argument a string that contains a space-
|
||||
separated list of schema node identifiers */
|
||||
if ((vec = clicon_strsep(arg, " \t\n", &nvec)) == NULL)
|
||||
goto done;
|
||||
for (i=0; i<nvec; i++){
|
||||
v = vec[i];
|
||||
if (yang_desc_schema_nodeid(yp, v, -1, &yres) < 0)
|
||||
goto done;
|
||||
if (yres == NULL){
|
||||
clicon_err(OE_YANG, 0, "schemanode sanity check of %s %s",
|
||||
yang_key2str(yang_keyword_get(ys)), v);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Y_DEVIATION:
|
||||
yspec = ys_spec(ys);
|
||||
if (yang_abs_schema_nodeid(yspec, ys, ys->ys_argument, -1, &yres) < 0)
|
||||
if (yang_abs_schema_nodeid(yspec, ys, arg, -1, &yres) < 0)
|
||||
goto done;
|
||||
if (yres == NULL){
|
||||
clicon_err(OE_YANG, 0, "schemanode sanity check of %s", ys->ys_argument);
|
||||
clicon_err(OE_YANG, 0, "schemanode sanity check of %s", arg);
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
|
|
@ -3104,11 +3125,11 @@ yang_datanode(yang_stmt *ys)
|
|||
* @retval 0 OK
|
||||
*/
|
||||
static int
|
||||
schema_nodeid_vec(yang_stmt *yn,
|
||||
char **vec,
|
||||
int nvec,
|
||||
schema_nodeid_vec(yang_stmt *yn,
|
||||
char **vec,
|
||||
int nvec,
|
||||
enum rfc_6020 keyword,
|
||||
yang_stmt **yres)
|
||||
yang_stmt **yres)
|
||||
{
|
||||
int retval = -1;
|
||||
char *arg;
|
||||
|
|
@ -3205,6 +3226,7 @@ yang_abs_schema_nodeid(yang_stmt *yspec,
|
|||
char *prefix = NULL;
|
||||
yang_stmt *yprefix;
|
||||
|
||||
*yres = NULL;
|
||||
/* check absolute schema_nodeid */
|
||||
if (schema_nodeid[0] != '/'){
|
||||
clicon_err(OE_YANG, EINVAL, "absolute schema nodeid should start with /");
|
||||
|
|
@ -3265,15 +3287,16 @@ yang_abs_schema_nodeid(yang_stmt *yspec,
|
|||
* Used in yang: unique, refine, uses augment
|
||||
*/
|
||||
int
|
||||
yang_desc_schema_nodeid(yang_stmt *yn,
|
||||
char *schema_nodeid,
|
||||
yang_desc_schema_nodeid(yang_stmt *yn,
|
||||
char *schema_nodeid,
|
||||
enum rfc_6020 keyword,
|
||||
yang_stmt **yres)
|
||||
yang_stmt **yres)
|
||||
{
|
||||
int retval = -1;
|
||||
char **vec = NULL;
|
||||
int nvec;
|
||||
|
||||
*yres = NULL;
|
||||
if (strlen(schema_nodeid) == 0)
|
||||
goto done;
|
||||
/* check absolute schema_nodeid */
|
||||
|
|
@ -3454,12 +3477,23 @@ ys_parse_sub(yang_stmt *ys,
|
|||
goto done;
|
||||
}
|
||||
break;
|
||||
case Y_UNKNOWN: /* XXX This code assumes ymod already loaded
|
||||
but it may not be */
|
||||
case Y_UNKNOWN:{ /* save (optional) argument in ys_cv */
|
||||
if (extra == NULL)
|
||||
break;
|
||||
ys->ys_extra = extra;
|
||||
if ((ys->ys_cv = cv_new(CGV_STRING)) == NULL){
|
||||
clicon_err(OE_YANG, errno, "cv_new");
|
||||
goto done;
|
||||
}
|
||||
if ((ret = cv_parse1(extra, ys->ys_cv, &reason)) < 0){ /* error */
|
||||
clicon_err(OE_YANG, errno, "parsing cv");
|
||||
goto done;
|
||||
}
|
||||
if (ret == 0){ /* parsing failed */
|
||||
clicon_err(OE_YANG, errno, "Parsing CV: %s", reason);
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -3629,3 +3663,4 @@ yang_key_match(yang_stmt *yn,
|
|||
cvec_free(cvv);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,8 +82,6 @@ struct yang_stmt{
|
|||
yang_stmt *ys_mymodule; /* Shortcut to "my" module. Augmented
|
||||
nodes can belong to other
|
||||
modules than the ancestor module */
|
||||
|
||||
char *ys_extra; /* For unknown */
|
||||
cg_var *ys_cv; /* cligen variable. See ys_populate()
|
||||
Following stmts have cv:s:
|
||||
leaf: for default value
|
||||
|
|
@ -91,7 +89,7 @@ struct yang_stmt{
|
|||
config: boolean true or false
|
||||
mandatory: boolean true or false
|
||||
fraction-digits for fraction-digits
|
||||
unknown-stmt (argument)
|
||||
unknown-stmt (optional argument)
|
||||
*/
|
||||
cvec *ys_cvec; /* List of stmt-specific variables
|
||||
Y_RANGE: range_min, range_max
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ identifier [A-Za-z_][A-Za-z0-9_\-\.]*
|
|||
|
||||
%%
|
||||
/* Common tokens */
|
||||
<KEYWORD,BOOLEAN,INTEGER,STRARG,STRING,UNKNOWN>[ \t]
|
||||
<KEYWORD,BOOLEAN,INTEGER,STRARG,STRING>[ \t]
|
||||
<KEYWORD,STRING,UNKNOWN,COMMENT2><<EOF>> { return MY_EOF; }
|
||||
<KEYWORD,BOOLEAN,INTEGER,STRARG,STRING,COMMENT1,UNKNOWN>\n { _YY->yy_linenum++; }
|
||||
<KEYWORD,BOOLEAN,INTEGER,STRARG,STRING,COMMENT1,UNKNOWN>\r
|
||||
|
|
@ -199,7 +199,8 @@ identifier [A-Za-z_][A-Za-z0-9_\-\.]*
|
|||
<UNKNOWN>; { BEGIN(KEYWORD); return *yytext; }
|
||||
<UNKNOWN>\" { _YY->yy_lex_string_state =UNKNOWN; BEGIN(STRINGDQ); return *yytext; }
|
||||
<UNKNOWN>\{ { BEGIN(KEYWORD); return *yytext; }
|
||||
<UNKNOWN>. { clixon_yang_parselval.string = strdup(yytext);
|
||||
<UNKNOWN>[ \t]+ { return SEP; }
|
||||
<UNKNOWN>[^{";: \t]+ { clixon_yang_parselval.string = strdup(yytext);
|
||||
return CHARS; }
|
||||
|
||||
<BOOLEAN>true { clixon_yang_parselval.string = strdup(yytext);
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@
|
|||
|
||||
%token MY_EOF
|
||||
%token SQ /* Single quote: ' */
|
||||
%token SEP /* Separators (at least one) */
|
||||
%token <string> CHARS
|
||||
%token <string> IDENTIFIER
|
||||
%token <string> BOOL
|
||||
|
|
@ -1540,26 +1541,33 @@ deviate_substmt : type_stmt { clicon_debug(2,"deviate-substmt -> type-st
|
|||
;
|
||||
|
||||
|
||||
/* For extensions XXX: we just drop the data */
|
||||
unknown_stmt : ustring ':' ustring ';'
|
||||
/* Represents the usage of an extension
|
||||
unknown-statement = prefix ":" identifier [sep string] optsep
|
||||
(";" /
|
||||
"{" optsep
|
||||
*((yang-stmt / unknown-statement) optsep)
|
||||
"}") stmt
|
||||
*
|
||||
*/
|
||||
unknown_stmt : ustring ':' ustring optsep ';'
|
||||
{ char *id; if ((id=string_del_join($1, ":", $3)) == NULL) _YYERROR("unknown_stmt");
|
||||
if (ysp_add(_yy, Y_UNKNOWN, id, NULL) == NULL) _YYERROR("unknown_stmt");
|
||||
clicon_debug(2,"unknown-stmt -> ustring : ustring");
|
||||
}
|
||||
| ustring ':' ustring string ';'
|
||||
| ustring ':' ustring SEP string optsep ';'
|
||||
{ char *id; if ((id=string_del_join($1, ":", $3)) == NULL) _YYERROR("unknown_stmt");
|
||||
if (ysp_add(_yy, Y_UNKNOWN, id, $4) == NULL){ _YYERROR("unknwon_stmt"); }
|
||||
if (ysp_add(_yy, Y_UNKNOWN, id, $5) == NULL){ _YYERROR("unknwon_stmt"); }
|
||||
clicon_debug(2,"unknown-stmt -> ustring : ustring string");
|
||||
}
|
||||
| ustring ':' ustring
|
||||
| ustring ':' ustring optsep
|
||||
{ char *id; if ((id=string_del_join($1, ":", $3)) == NULL) _YYERROR("unknown_stmt");
|
||||
if (ysp_add_push(_yy, Y_UNKNOWN, id, NULL) == NULL) _YYERROR("unknown_stmt"); }
|
||||
'{' yang_stmts '}'
|
||||
{ if (ystack_pop(_yy) < 0) _YYERROR("unknown_stmt");
|
||||
clicon_debug(2,"unknown-stmt -> ustring : ustring { yang-stmts }"); }
|
||||
| ustring ':' ustring string
|
||||
| ustring ':' ustring SEP string optsep
|
||||
{ char *id; if ((id=string_del_join($1, ":", $3)) == NULL) _YYERROR("unknown_stmt");
|
||||
if (ysp_add_push(_yy, Y_UNKNOWN, id, $4) == NULL) _YYERROR("unknown_stmt"); }
|
||||
if (ysp_add_push(_yy, Y_UNKNOWN, id, $5) == NULL) _YYERROR("unknown_stmt"); }
|
||||
'{' yang_stmts '}'
|
||||
{ if (ystack_pop(_yy) < 0) _YYERROR("unknown_stmt");
|
||||
clicon_debug(2,"unknown-stmt -> ustring : ustring string { yang-stmts }"); }
|
||||
|
|
@ -1815,6 +1823,11 @@ node_identifier : IDENTIFIER
|
|||
identifier_ref : node_identifier { $$=$1;}
|
||||
;
|
||||
|
||||
optsep : SEP
|
||||
|
|
||||
;
|
||||
|
||||
|
||||
stmtend : ';'
|
||||
| '{' '}'
|
||||
| '{' unknown_stmt '}'
|
||||
|
|
|
|||
115
test/lib.sh
115
test/lib.sh
|
|
@ -34,7 +34,7 @@ if [ -f ./site.sh ]; then
|
|||
# test skiplist.
|
||||
for f in $SKIPLIST; do
|
||||
if [ "$testfile" = "$f" ]; then
|
||||
echo ...skipped
|
||||
echo "...skipped (see site.sh)"
|
||||
return -1 # skip
|
||||
fi
|
||||
done
|
||||
|
|
@ -233,9 +233,11 @@ new(){
|
|||
# Arguments:
|
||||
# - command,
|
||||
# - expected command return value (0 if OK)
|
||||
# - expected stdout outcome,
|
||||
# - expected2 stdout outcome,
|
||||
# Example: expectfn "$clixon_cli -1 -f $cfg show conf cli" 0 "^$"
|
||||
# - expected* stdout outcome, (can be many)
|
||||
# Example: expectfn "$clixon_cli -1 -f $cfg show conf cli" 0 "line1" "line2"
|
||||
# XXX: for some reason some curl commands dont work here, eg
|
||||
# curl -H 'Accept: application/xrd+xml'
|
||||
# instead use expectpart
|
||||
expectfn(){
|
||||
cmd=$1
|
||||
retval=$2
|
||||
|
|
@ -258,32 +260,30 @@ expectfn(){
|
|||
echo -e "\e[0m:"
|
||||
exit -1
|
||||
fi
|
||||
# if [ $r != 0 ]; then
|
||||
# return
|
||||
# fi
|
||||
# if [ $ret -ne $retval ]; then
|
||||
# echo -e "\e[31m\nError in Test$testnr [$testname]:"
|
||||
# echo -e "\e[0m:"
|
||||
# exit -1
|
||||
# fi
|
||||
# Match if both are empty string
|
||||
# Match if both are empty string (special case)
|
||||
if [ -z "$ret" -a -z "$expect" ]; then
|
||||
return
|
||||
fi
|
||||
if [ -z "$ret" -a "$expect" = "^$" ]; then
|
||||
return
|
||||
fi
|
||||
# grep extended grep
|
||||
match=`echo $ret | grep -EZo "$expect"`
|
||||
if [ -z "$match" ]; then
|
||||
err "$expect" "$ret"
|
||||
fi
|
||||
if [ -n "$expect2" ]; then
|
||||
match=`echo "$ret" | grep -EZo "$expect2"`
|
||||
if [ -z "$match" ]; then
|
||||
err $expect "$ret"
|
||||
# Loop over all variable args expect strings
|
||||
let i=0;
|
||||
for exp in "$@"; do
|
||||
if [ $i -gt 1 ]; then
|
||||
match=`echo $ret | grep -EZo "$exp"`
|
||||
if [ -z "$match" ]; then
|
||||
err "$exp" "$ret"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
let i++;
|
||||
|
||||
done
|
||||
}
|
||||
|
||||
# Evaluate and return
|
||||
|
|
@ -313,6 +313,48 @@ expecteq(){
|
|||
fi
|
||||
}
|
||||
|
||||
# Evaluate and return
|
||||
# like expecteq but partial match is OK
|
||||
# Example: expecteq $(fn arg) 0 "my return"
|
||||
# - evaluated expression
|
||||
# - expected command return value (0 if OK)
|
||||
# - expected stdout outcome
|
||||
expectpart(){
|
||||
r=$?
|
||||
ret=$1
|
||||
retval=$2
|
||||
expect=$3
|
||||
# echo "r:$r"
|
||||
# echo "ret:\"$ret\""
|
||||
# echo "retval:$retval"
|
||||
# echo "expect:$expect"
|
||||
if [ $r != $retval ]; then
|
||||
echo -e "\e[31m\nError ($r != $retval) in Test$testnr [$testname]:"
|
||||
echo -e "\e[0m:"
|
||||
exit -1
|
||||
fi
|
||||
if [ -z "$ret" -a -z "$expect" ]; then
|
||||
return
|
||||
fi
|
||||
# Loop over all variable args expect strings
|
||||
let i=0;
|
||||
for exp in "$@"; do
|
||||
if [ $i -gt 1 ]; then
|
||||
# echo "exp:$exp"
|
||||
match=`echo $ret | grep -Zo "$exp"` # XXX -EZo: -E cant handle {}
|
||||
if [ -z "$match" ]; then
|
||||
err "$exp" "$ret"
|
||||
fi
|
||||
fi
|
||||
let i++;
|
||||
done
|
||||
|
||||
# if [[ "$ret" != "$expect" ]]; then
|
||||
# err "$expect" "$ret"
|
||||
# fi
|
||||
}
|
||||
|
||||
|
||||
# Pipe stdin to command
|
||||
# Arguments:
|
||||
# - Command
|
||||
|
|
@ -401,6 +443,43 @@ EOF
|
|||
fi
|
||||
}
|
||||
|
||||
# Like expecteof/expecteofx but with test == instead of grep.
|
||||
# No wildcards
|
||||
# Use this for multi-lines
|
||||
expecteofeq(){
|
||||
cmd=$1
|
||||
retval=$2
|
||||
input=$3
|
||||
expect=$4
|
||||
|
||||
# Do while read stuff
|
||||
ret=$($cmd<<EOF
|
||||
$input
|
||||
EOF
|
||||
)
|
||||
r=$?
|
||||
if [ $r != $retval ]; then
|
||||
echo -e "\e[31m\nError ($r != $retval) in Test$testnr [$testname]:"
|
||||
echo -e "\e[0m:"
|
||||
exit -1
|
||||
fi
|
||||
# If error dont match output strings (why not?)
|
||||
# if [ $r != 0 ]; then
|
||||
# return
|
||||
# fi
|
||||
# Match if both are empty string
|
||||
if [ -z "$ret" -a -z "$expect" ]; then
|
||||
return
|
||||
fi
|
||||
# echo "ret:\"$ret\""
|
||||
# echo "expect:\"$expect\""
|
||||
# echo "match:\"$match\""
|
||||
if [ "$ret" != "$expect" ]; then
|
||||
err "$expect" "$ret"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# clixon tester read from file for large tests
|
||||
expecteof_file(){
|
||||
cmd=$1
|
||||
|
|
|
|||
|
|
@ -171,17 +171,17 @@ new "restconf DELETE whole datastore"
|
|||
expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 ""
|
||||
|
||||
new "restconf set protocol tcp+udp fail"
|
||||
expecteq "$(curl -s -X PUT http://localhost/restconf/data/system:system/protocol -d '{"system:protocol":{"tcp": null, "udp": null}}')" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "bad-element","error-info": {"bad-element": "udp"},"error-severity": "error","error-message": "Element in choice statement already exists"}}}
'
|
||||
expecteq "$(curl -s -X PUT http://localhost/restconf/data/system:system/protocol -d '{"system:protocol":{"tcp": null, "udp": null}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"udp"},"error-severity":"error","error-message":"Element in choice statement already exists"}}}
'
|
||||
|
||||
new "restconf set protocol tcp"
|
||||
expecteq "$(curl -s -X PUT http://localhost/restconf/data/system:system/protocol -d {\"system:protocol\":{\"tcp\":null}})" 0 ""
|
||||
|
||||
new "restconf get protocol tcp"
|
||||
expecteq "$(curl -s -X GET http://localhost/restconf/data/system:system)" 0 '{"system:system": {"protocol": {"tcp": null}}}
|
||||
expecteq "$(curl -s -X GET http://localhost/restconf/data/system:system)" 0 '{"system:system":{"protocol":{"tcp":null}}}
|
||||
'
|
||||
|
||||
new "restconf set protocol tcp+udp fail"
|
||||
expecteq "$(curl -s -X PUT http://localhost/restconf/data/system:system/protocol -d '{"system:protocol":{"tcp": null, "udp": null}}')" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "bad-element","error-info": {"bad-element": "udp"},"error-severity": "error","error-message": "Element in choice statement already exists"}}}
'
|
||||
expecteq "$(curl -s -X PUT http://localhost/restconf/data/system:system/protocol -d '{"system:protocol":{"tcp": null, "udp": null}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"udp"},"error-severity":"error","error-message":"Element in choice statement already exists"}}}
'
|
||||
|
||||
new "cli set protocol udp"
|
||||
expectfn "$clixon_cli -1 -f $cfg -l o set system protocol udp" 0 "^$"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#!/bin/bash
|
||||
# Test: JSON parser tests
|
||||
# Test: JSON parser tests. See RFC7951
|
||||
# - Multi-line + pretty-print
|
||||
# - Empty values
|
||||
# Note that members should not be quoted. See test_restconf2.sh for typed
|
||||
#PROG="valgrind --leak-check=full --show-leak-kinds=all ../util/clixon_util_json"
|
||||
# Magic line must be first in script (see README.md)
|
||||
|
|
@ -7,20 +9,7 @@ s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
|||
|
||||
: ${clixon_util_json:=clixon_util_json}
|
||||
|
||||
new "json parse to xml"
|
||||
expecteofx "$clixon_util_json" 0 '{"foo": -23}' "<foo>-23</foo>"
|
||||
|
||||
new "json parse to json" # should be {"foo": -23}
|
||||
expecteofx "$clixon_util_json -j" 0 '{"foo": -23}' '{"foo": "-23"}'
|
||||
|
||||
new "json parse list xml"
|
||||
expecteofx "$clixon_util_json" 0 '{"a":[0,1,2,3]}' "<a>0</a><a>1</a><a>2</a><a>3</a>"
|
||||
|
||||
new "json parse list json" # should be {"a":[0,1,2,3]}
|
||||
expecteofx "$clixon_util_json -j" 0 '{"a":[0,1,2,3]}' '{"a": "0"}{"a": "1"}{"a": "2"}{"a": "3"}'
|
||||
|
||||
fyang=$dir/json.yang
|
||||
fjson=$dir/json.json
|
||||
cat <<EOF > $fyang
|
||||
module json{
|
||||
prefix ex;
|
||||
|
|
@ -39,20 +28,58 @@ module json{
|
|||
}
|
||||
EOF
|
||||
|
||||
JSON='{"json:a": -23}'
|
||||
new "test params: -y $fyang"
|
||||
|
||||
# No yang
|
||||
new "json parse to xml"
|
||||
expecteofx "$clixon_util_json" 0 '{"foo": -23}' "<foo>-23</foo>"
|
||||
|
||||
new "json parse to json" # should be {"foo": -23}
|
||||
expecteofx "$clixon_util_json -j" 0 '{"foo": -23}' '{"foo":"-23"}'
|
||||
|
||||
new "json parse list xml"
|
||||
expecteofx "$clixon_util_json" 0 '{"a":[0,1,2,3]}' "<a>0</a><a>1</a><a>2</a><a>3</a>"
|
||||
|
||||
new "json parse list json" # should be {"a":[0,1,2,3]}
|
||||
expecteofx "$clixon_util_json -j" 0 '{"a":[0,1,2,3]}' '{"a":"0"}{"a":"1"}{"a":"2"}{"a":"3"}'
|
||||
|
||||
# Multi-line JOSN not pretty-print
|
||||
JSON='{"json:c":{"a":42,"s":"string"}}'
|
||||
# Same with pretty-print
|
||||
JSONP='{
|
||||
"json:c": {
|
||||
"a": 42,
|
||||
"s": "string"
|
||||
}
|
||||
}'
|
||||
|
||||
new "json no pp in/out"
|
||||
expecteofx "$clixon_util_json -jy $fyang" 0 "$JSON" "$JSON"
|
||||
|
||||
new "json pp in/out"
|
||||
expecteofeq "$clixon_util_json -jpy $fyang" 0 "$JSONP" "$JSONP"
|
||||
|
||||
new "json pp in/ no pp out"
|
||||
expecteofeq "$clixon_util_json -jy $fyang" 0 "$JSONP" "$JSON"
|
||||
|
||||
new "json no pp in/ pp out"
|
||||
expecteofeq "$clixon_util_json -jpy $fyang" 0 "$JSON" "$JSONP"
|
||||
|
||||
JSON='{"json:a":-23}'
|
||||
|
||||
new "json leaf back to json"
|
||||
expecteofx "$clixon_util_json -j -y $fyang" 0 "$JSON" "$JSON"
|
||||
|
||||
JSON='{"json:c": {"a": 937}}'
|
||||
JSON='{"json:c":{"a":937}}'
|
||||
new "json parse container back to json"
|
||||
expecteofx "$clixon_util_json -j -y $fyang" 0 "$JSON" "$JSON"
|
||||
|
||||
# This is wrong
|
||||
# This should work
|
||||
if false; then
|
||||
JSON='{"json:c": {"s": "<![CDATA[ z > x & x < y ]]>"}}'
|
||||
new "json parse cdata xml"
|
||||
expecteofx "$clixon_util_json -j -y $fyang" 0 "$JSON" "$JSON"
|
||||
fi
|
||||
|
||||
|
||||
rm -rf $dir
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ wait_backend
|
|||
wait_restconf
|
||||
|
||||
new "auth get"
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-type": "application","error-tag": "invalid-value","error-severity": "error","error-message": "Instance does not exist"}}}}
'
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"rpc-error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}}
'
|
||||
|
||||
# explicitly disable nacm (regression on netgate bug)
|
||||
new "disable nacm"
|
||||
|
|
@ -145,13 +145,13 @@ new "commit it"
|
|||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "auth get (no user: access denied)"
|
||||
expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}}
'
|
||||
expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}}
'
|
||||
|
||||
new "auth get (wrong passwd: access denied)"
|
||||
expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}}
'
|
||||
expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}}
'
|
||||
|
||||
new "auth get (access)"
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x": 0}
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0}
|
||||
'
|
||||
|
||||
#----------------Enable NACM
|
||||
|
|
@ -160,24 +160,24 @@ new "enable nacm"
|
|||
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 ""
|
||||
|
||||
new "admin get nacm"
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x": 0}
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0}
|
||||
'
|
||||
|
||||
new "limited get nacm"
|
||||
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x": 0}
|
||||
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0}
|
||||
'
|
||||
|
||||
new "guest get nacm"
|
||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}}
'
|
||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
||||
|
||||
new "admin edit nacm"
|
||||
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"nacm-example:x": 1}' http://localhost/restconf/data/nacm-example:x)" 0 ""
|
||||
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"nacm-example:x":1}' http://localhost/restconf/data/nacm-example:x)" 0 ""
|
||||
|
||||
new "limited edit nacm"
|
||||
expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"nacm-example:x": 2}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}}
'
|
||||
expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"nacm-example:x": 2}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||
|
||||
new "guest edit nacm"
|
||||
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"nacm-example:x": 3}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}}
'
|
||||
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"nacm-example:x": 3}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
||||
|
||||
new "Kill restconf daemon"
|
||||
stop_restconf
|
||||
|
|
|
|||
|
|
@ -110,12 +110,12 @@ EOF
|
|||
|
||||
#----------- First get
|
||||
case "$ret1" in
|
||||
0) ret='{"nacm-example:x": 42}
|
||||
0) ret='{"nacm-example:x":42}
|
||||
'
|
||||
;;
|
||||
1) ret='{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}}
'
|
||||
1) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||
;;
|
||||
2) ret='{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-type": "application","error-tag": "invalid-value","error-severity": "error","error-message": "Instance does not exist"}}}}
'
|
||||
2) ret='{"ietf-restconf:errors":{"error":{"rpc-error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}}
'
|
||||
;;
|
||||
esac
|
||||
|
||||
|
|
@ -126,7 +126,7 @@ EOF
|
|||
case "$ret2" in
|
||||
0) ret=''
|
||||
;;
|
||||
1) ret='{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}}
'
|
||||
1) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||
;;
|
||||
esac
|
||||
new "edit new 99"
|
||||
|
|
@ -134,14 +134,14 @@ EOF
|
|||
|
||||
#----------- Then second get
|
||||
case "$ret3" in
|
||||
0) ret='{"nacm-example:x": 99}
|
||||
0) ret='{"nacm-example:x":99}
|
||||
'
|
||||
;;
|
||||
1) ret='{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}}
'
|
||||
1) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||
;;
|
||||
2) ret='{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-type": "application","error-tag": "invalid-value","error-severity": "error","error-message": "Instance does not exist"}}}}
'
|
||||
2) ret='{"ietf-restconf:errors":{"error":{"rpc-error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}}
'
|
||||
;;
|
||||
3) ret='{"nacm-example:x": 42}
|
||||
3) ret='{"nacm-example:x":42}
|
||||
'
|
||||
esac
|
||||
new "get 99"
|
||||
|
|
|
|||
|
|
@ -153,41 +153,41 @@ wait_backend
|
|||
wait_restconf
|
||||
|
||||
new "auth get"
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data)" 0 '{"data": {"clixon-example:state": {"op": ["42","41","43"]}}}
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data)" 0 '{"data":{"clixon-example:state":{"op":["42","41","43"]}}}
|
||||
'
|
||||
|
||||
new "Set x to 0"
|
||||
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"nacm-example:x": 0}' http://localhost/restconf/data/nacm-example:x)" 0 ""
|
||||
|
||||
new "auth get (no user: access denied)"
|
||||
expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}}
'
|
||||
expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}}
'
|
||||
|
||||
new "auth get (wrong passwd: access denied)"
|
||||
expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}}
'
|
||||
expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}}
'
|
||||
|
||||
new "auth get (access)"
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x": 0}
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0}
|
||||
'
|
||||
|
||||
new "admin get nacm"
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x": 0}
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0}
|
||||
'
|
||||
|
||||
new "limited get nacm"
|
||||
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x": 0}
|
||||
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0}
|
||||
'
|
||||
|
||||
new "guest get nacm"
|
||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}}
'
|
||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
||||
|
||||
new "admin edit nacm"
|
||||
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"nacm-example:x": 1}' http://localhost/restconf/data/nacm-example:x)" 0 ""
|
||||
|
||||
new "limited edit nacm"
|
||||
expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"nacm-example:x": 2}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}}
'
|
||||
expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"nacm-example:x": 2}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||
|
||||
new "guest edit nacm"
|
||||
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"nacm-example:x": 3}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}}
'
|
||||
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"nacm-example:x": 3}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
||||
|
||||
new "cli show conf as admin"
|
||||
expectfn "$clixon_cli -1 -U andy -l o -f $cfg show conf" 0 "^x 1;$"
|
||||
|
|
|
|||
|
|
@ -160,27 +160,27 @@ expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": tru
|
|||
#----READ access
|
||||
#user:admin
|
||||
new "admin read ok"
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate)" 0 '{"clixon-example:translate": [{"k": "key42","value": "val42"},{ "k": "key43","value": "val43"}]}
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate)" 0 '{"clixon-example:translate":[{"k":"key42","value":"val42"},{"k":"key43","value":"val43"}]}
|
||||
'
|
||||
|
||||
new "admin read netconf ok"
|
||||
expecteof "$clixon_netconf -U andy -qf $cfg" 0 '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/t:translate" xmlns:t="urn:example:clixon" /></get-config></rpc>]]>]]>' '^<rpc-reply><data><translate xmlns="urn:example:clixon"><k>key42</k><value>val42</value></translate><translate xmlns="urn:example:clixon"><k>key43</k><value>val43</value></translate></data></rpc-reply>]]>]]>$'
|
||||
|
||||
new "admin read element ok"
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate=key42/value)" 0 '{"clixon-example:value": "val42"}
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate=key42/value)" 0 '{"clixon-example:value":"val42"}
|
||||
'
|
||||
|
||||
new "admin read other module OK"
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x": 42}
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":42}
|
||||
'
|
||||
|
||||
new "admin read state OK"
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state": {"op": ["42","41","43"]}}
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["42","41","43"]}}
|
||||
'
|
||||
|
||||
new "admin read top ok (all)"
|
||||
ret=$(curl -u andy:bar -sS -X GET http://localhost/restconf/data)
|
||||
expect='{"data": {"nacm-example:x": 42,"clixon-example:translate":'
|
||||
expect='{"data":{"nacm-example:x":42,"clixon-example:translate":'
|
||||
match=`echo $ret | grep -EZo "$expect"`
|
||||
if [ -z "$match" ]; then
|
||||
err "$expect" "$ret"
|
||||
|
|
@ -189,65 +189,65 @@ fi
|
|||
#user:limit
|
||||
|
||||
new "limit read ok"
|
||||
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate)" 0 '{"clixon-example:translate": [{"k": "key42","value": "val42"},{ "k": "key43","value": "val43"}]}
|
||||
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate)" 0 '{"clixon-example:translate":[{"k":"key42","value":"val42"},{"k":"key43","value":"val43"}]}
|
||||
'
|
||||
|
||||
new "limit read netconf ok"
|
||||
expecteof "$clixon_netconf -U wilma -qf $cfg" 0 '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/t:translate" xmlns:t="urn:example:clixon"/></get-config></rpc>]]>]]>' '^<rpc-reply><data><translate xmlns="urn:example:clixon"><k>key42</k><value>val42</value></translate><translate xmlns="urn:example:clixon"><k>key43</k><value>val43</value></translate></data></rpc-reply>]]>]]>$'
|
||||
|
||||
new "limit read element ok"
|
||||
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate=key42/value)" 0 '{"clixon-example:value": "val42"}
|
||||
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate=key42/value)" 0 '{"clixon-example:value":"val42"}
|
||||
'
|
||||
|
||||
new "limit read other module fail"
|
||||
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-type": "application","error-tag": "invalid-value","error-severity": "error","error-message": "Instance does not exist"}}}}
'
|
||||
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"rpc-error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}}
'
|
||||
|
||||
new "limit read state OK"
|
||||
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state": {"op": ["42","41","43"]}}
|
||||
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["42","41","43"]}}
|
||||
'
|
||||
|
||||
new "limit read top ok (part)"
|
||||
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data)" 0 '{"data": {"clixon-example:translate": [{"k": "key42","value": "val42"},{ "k": "key43","value": "val43"}],"clixon-example:state": {"op": ["42","41","43"]}}}
|
||||
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data)" 0 '{"data":{"clixon-example:translate":[{"k":"key42","value":"val42"},{"k":"key43","value":"val43"}],"clixon-example:state":{"op":["42","41","43"]}}}
|
||||
'
|
||||
|
||||
#user:guest
|
||||
|
||||
new "guest read fail"
|
||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}}
'
|
||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||
|
||||
new "guest read netconf fail"
|
||||
expecteof "$clixon_netconf -U guest -qf $cfg" 0 '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/t:translate" xmlns:t="urn:example:clixon"/></get-config></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>access-denied</error-tag><error-severity>error</error-severity><error-message>default deny</error-message></rpc-error></rpc-reply>]]>]]>$'
|
||||
|
||||
new "guest read element fail"
|
||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate=key42/value)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}}
'
|
||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate=key42/value)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||
|
||||
new "guest read other module fail"
|
||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}}
'
|
||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||
|
||||
new "guest read state fail"
|
||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}}
'
|
||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||
|
||||
new "guest read top ok (part)"
|
||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}}
'
|
||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||
|
||||
#------- RPC operation
|
||||
|
||||
new "admin rpc ok"
|
||||
expecteq "$(curl -u andy:bar -s -X POST -d '{"clixon-example:input":{"x":"78"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output": {"x": "78","y": "42"}}
|
||||
expecteq "$(curl -u andy:bar -s -X POST -d '{"clixon-example:input":{"x":"78"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"78","y":"42"}}
|
||||
'
|
||||
|
||||
new "admin rpc netconf ok"
|
||||
expecteof "$clixon_netconf -U andy -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><example xmlns="urn:example:clixon"><x>0</x></example></rpc>]]>]]>' 0 '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><x xmlns="urn:example:clixon">0</x><y xmlns="urn:example:clixon">42</y></rpc-reply>]]>]]>$'
|
||||
|
||||
new "limit rpc ok"
|
||||
expecteq "$(curl -u wilma:bar -s -X POST http://localhost/restconf/operations/clixon-example:example -d '{"clixon-example:input":{"x":42}}' )" 0 '{"clixon-example:output": {"x": "42","y": "42"}}
|
||||
expecteq "$(curl -u wilma:bar -s -X POST http://localhost/restconf/operations/clixon-example:example -d '{"clixon-example:input":{"x":42}}' )" 0 '{"clixon-example:output":{"x":"42","y":"42"}}
|
||||
'
|
||||
|
||||
new "limit rpc netconf ok"
|
||||
expecteof "$clixon_netconf -U wilma -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><example xmlns="urn:example:clixon"><x>0</x></example></rpc>]]>]]>' 0 '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><x xmlns="urn:example:clixon">0</x><y xmlns="urn:example:clixon">42</y></rpc-reply>]]>]]>$'
|
||||
|
||||
new "guest rpc fail"
|
||||
expecteq "$(curl -u guest:bar -s -X POST http://localhost/restconf/operations/clixon-example:example -d '{"clixon-example:input":{"x":42}}' )" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}}
'
|
||||
expecteq "$(curl -u guest:bar -s -X POST http://localhost/restconf/operations/clixon-example:example -d '{"clixon-example:input":{"x":42}}' )" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
||||
|
||||
new "guest rpc netconf fail"
|
||||
expecteof "$clixon_netconf -U guest -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><example xmlns="urn:example:clixon"><x>0</x></example></rpc>]]>]]>' 0 '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><rpc-error><error-type>application</error-type><error-tag>access-denied</error-tag><error-severity>error</error-severity><error-message>access denied</error-message></rpc-error></rpc-reply>]]>]]>$'
|
||||
|
|
@ -255,18 +255,18 @@ expecteof "$clixon_netconf -U guest -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml
|
|||
#------------------ Set read-default permit
|
||||
|
||||
new "admin set read-default permit"
|
||||
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:read-default": "permit"}' http://localhost/restconf/data/ietf-netconf-acm:nacm/read-default)" 0 ""
|
||||
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:read-default":"permit"}' http://localhost/restconf/data/ietf-netconf-acm:nacm/read-default)" 0 ""
|
||||
|
||||
new "limit read ok"
|
||||
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate)" 0 '{"clixon-example:translate": [{"k": "key42","value": "val42"},{ "k": "key43","value": "val43"}]}
|
||||
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate)" 0 '{"clixon-example:translate":[{"k":"key42","value":"val42"},{"k":"key43","value":"val43"}]}
|
||||
'
|
||||
|
||||
new "limit read other module ok"
|
||||
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x": 42}
|
||||
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":42}
|
||||
'
|
||||
|
||||
new "guest read state fail"
|
||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}}
'
|
||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||
|
||||
|
||||
new "Kill restconf daemon"
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ nacm
|
|||
|
||||
#----------root
|
||||
new "update root list default deny"
|
||||
expecteq "$(curl -u wilma:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data -d '<data><x xmlns="urn:example:nacm">42</x>$RULES</data>')" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}}
'
|
||||
expecteq "$(curl -u wilma:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data -d '<data><x xmlns="urn:example:nacm">42</x>$RULES</data>')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||
|
||||
# replace all, then must include NACM rules as well
|
||||
MSG="<data>$RULES</data>"
|
||||
|
|
@ -186,7 +186,7 @@ new "update root list permit"
|
|||
expecteq "$(curl -u andy:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data -d "$MSG")" 0 ''
|
||||
|
||||
new "delete root list deny"
|
||||
expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}}
'
|
||||
expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||
|
||||
new "delete root permit"
|
||||
expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" 0 ''
|
||||
|
|
@ -196,62 +196,62 @@ nacm
|
|||
|
||||
#----------leaf
|
||||
new "create leaf deny"
|
||||
expecteq "$(curl -u guest:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:x -d '<x xmlns="urn:example:nacm">42</x>')" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}}
'
|
||||
expecteq "$(curl -u guest:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:x -d '<x xmlns="urn:example:nacm">42</x>')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
||||
|
||||
new "create leaf permit"
|
||||
expecteq "$(curl -u wilma:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:x -d '<x xmlns="urn:example:nacm">42</x>')" 0 ''
|
||||
|
||||
new "update leaf deny"
|
||||
expecteq "$(curl -u wilma:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:x -d '<x xmlns="urn:example:nacm">99</x>')" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}}
'
|
||||
expecteq "$(curl -u wilma:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:x -d '<x xmlns="urn:example:nacm">99</x>')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
||||
|
||||
new "update leaf permit"
|
||||
expecteq "$(curl -u guest:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:x -d '<x xmlns="urn:example:nacm">99</x>')" 0 ''
|
||||
|
||||
new "read leaf check"
|
||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x": 99}
|
||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":99}
|
||||
'
|
||||
|
||||
new "delete leaf deny"
|
||||
expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}}
'
|
||||
expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
||||
|
||||
new "delete leaf permit"
|
||||
expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data/nacm-example:x)" 0 ''
|
||||
|
||||
#----- list/container
|
||||
new "create list deny"
|
||||
expecteq "$(curl -u guest:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:a=key42 -d '<a xmlns="urn:example:nacm"><k>key42</k><b><c>str</c></b></a>')" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}}
'
|
||||
expecteq "$(curl -u guest:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:a=key42 -d '<a xmlns="urn:example:nacm"><k>key42</k><b><c>str</c></b></a>')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
||||
|
||||
new "create list permit"
|
||||
expecteq "$(curl -u wilma:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:a=key42 -d '<a xmlns="urn:example:nacm"><k>key42</k><b><c>str</c></b></a>')" 0 ''
|
||||
|
||||
new "update list deny"
|
||||
expecteq "$(curl -u wilma:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:a=key42 -d '<a xmlns="urn:example:nacm"><k>key42</k><b><c>update</c></b></a>')" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}}
'
|
||||
expecteq "$(curl -u wilma:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:a=key42 -d '<a xmlns="urn:example:nacm"><k>key42</k><b><c>update</c></b></a>')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
||||
|
||||
new "update list permit"
|
||||
expecteq "$(curl -u guest:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:a=key42 -d '<a xmlns="urn:example:nacm"><k>key42</k><b><c>update</c></b></a>')" 0 ''
|
||||
|
||||
new "read list check"
|
||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:a)" 0 '{"nacm-example:a": [{"k": "key42","b": {"c": "update"}}]}
|
||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:a)" 0 '{"nacm-example:a":[{"k":"key42","b":{"c":"update"}}]}
|
||||
'
|
||||
|
||||
new "delete list deny"
|
||||
expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data/nacm-example:a=key42)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}}
'
|
||||
expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data/nacm-example:a=key42)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
||||
|
||||
new "delete list permit"
|
||||
expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data/nacm-example:a=key42)" 0 ''
|
||||
|
||||
#----- default deny (clixon-example limit and guest have default access)
|
||||
new "default create list deny"
|
||||
expecteq "$(curl -u wilma:bar -sS -X PUT http://localhost/restconf/data/clixon-example:translate=key42 -d '{"clixon-example:translate": [{"k": "key42","value": "val42"}]}')" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}}
'
|
||||
expecteq "$(curl -u wilma:bar -sS -X PUT http://localhost/restconf/data/clixon-example:translate=key42 -d '{"clixon-example:translate": [{"k":"key42","value":"val42"}]}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||
|
||||
new "create list permit"
|
||||
expecteq "$(curl -u andy:bar -sS -X PUT http://localhost/restconf/data/clixon-example:translate=key42 -d '{"clixon-example:translate": [{"k": "key42","value": "val42"}]}')" 0 ''
|
||||
expecteq "$(curl -u andy:bar -sS -X PUT http://localhost/restconf/data/clixon-example:translate=key42 -d '{"clixon-example:translate": [{"k":"key42","value":"val42"}]}')" 0 ''
|
||||
|
||||
new "default update list deny"
|
||||
expecteq "$(curl -u wilma:bar -sS -X PUT http://localhost/restconf/data/clixon-example:translate=key42 -d '{"clixon-example:translate": [{"k": "key42","value": "val99"}]}')" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}}
'
|
||||
expecteq "$(curl -u wilma:bar -sS -X PUT http://localhost/restconf/data/clixon-example:translate=key42 -d '{"clixon-example:translate": [{"k":"key42","value":"val99"}]}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||
|
||||
new "default delete list deny"
|
||||
expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data/clixon-example:translate=key42)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}}
'
|
||||
expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data/clixon-example:translate=key42)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||
|
||||
new "Kill restconf daemon"
|
||||
stop_restconf
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": tru
|
|||
#--------------- nacm enabled
|
||||
|
||||
new "admin get nacm"
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x": 0}
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0}
|
||||
'
|
||||
|
||||
# Rule 1: deny-kill-session
|
||||
|
|
@ -185,14 +185,14 @@ new "deny-delete-config: limited fail (netconf)"
|
|||
expecteof "$clixon_netconf -qf $cfg -U wilma" 0 "<rpc><delete-config><target><startup/></target></delete-config></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>access-denied</error-tag><error-severity>error</error-severity><error-message>access denied</error-message></rpc-error></rpc-reply>]]>]]>$"
|
||||
|
||||
new "deny-delete-config: guest fail (restconf)"
|
||||
expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}}
'
|
||||
expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||
|
||||
# In restconf delete-config is translated to edit-config which is permitted
|
||||
new "deny-delete-config: limited fail (restconf) ok"
|
||||
expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data)" 0 ''
|
||||
|
||||
new "admin get nacm (should fail)"
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-type": "application","error-tag": "invalid-value","error-severity": "error","error-message": "Instance does not exist"}}}}
'
|
||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"rpc-error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}}
'
|
||||
|
||||
new "deny-delete-config: admin ok (restconf)"
|
||||
expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" 0 ''
|
||||
|
|
@ -209,10 +209,10 @@ expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": tru
|
|||
|
||||
# Rule 3: permit-edit-config
|
||||
new "permit-edit-config: limited ok restconf"
|
||||
expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"nacm-example:x": 2}' http://localhost/restconf/data/nacm-example:x)" 0 ''
|
||||
expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"nacm-example:x":2}' http://localhost/restconf/data/nacm-example:x)" 0 ''
|
||||
|
||||
new "permit-edit-config: guest fail restconf"
|
||||
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"nacm-example:x": 2}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}}
'
|
||||
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"nacm-example:x":2}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||
|
||||
new "Kill restconf daemon"
|
||||
stop_restconf
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ done | $clixon_netconf -qf $cfg > /dev/null; } 2>&1 | awk '/real/ {print $2}'
|
|||
|
||||
# RESTCONF get
|
||||
new "restconf get test single req XXX"
|
||||
expecteq "$(curl -s -X GET http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1)" 0 '{"ietf-interfaces:interface": [{"name": "e1","type": "ex:eth","enabled": true,"oper-status": "up"}]}
|
||||
expecteq "$(curl -s -X GET http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1)" 0 '{"ietf-interfaces:interface":[{"name":"e1","type":"ex:eth","enabled":true,"oper-status":"up"}]}
|
||||
'
|
||||
|
||||
new "restconf get $perfreq single reqs"
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ cat <<EOF > $cfg
|
|||
EOF
|
||||
|
||||
# This is a fixed 'state' implemented in routing_backend. It is assumed to be always there
|
||||
state='{"clixon-example:state": {"op": ["42","41","43"]}}
'
|
||||
state='{"clixon-example:state":{"op":\["42","41","43"\]}'
|
||||
|
||||
new "test params: -f $cfg -- -s"
|
||||
if [ $BE -ne 0 ]; then
|
||||
|
|
@ -64,12 +64,12 @@ expecteq "$(curl -s -X GET http://localhost/.well-known/host-meta)" 0 "<XRD xmln
|
|||
</XRD>
"
|
||||
|
||||
new "restconf get restconf resource. RFC 8040 3.3 (json)"
|
||||
expecteq "$(curl -sG http://localhost/restconf)" 0 '{"restconf": {"data": null,"operations": null,"yang-library-version": "2016-06-21"}}
|
||||
expecteq "$(curl -sG http://localhost/restconf)" 0 '{"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?
|
||||
expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/restconf)" 0 '<restconf><data/><operations/><yang-library-version>2016-06-21</yang-library-version></restconf>
|
||||
expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/restconf)" 0 '<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
|
||||
|
|
@ -86,7 +86,7 @@ if [ -z "$match" ]; then
|
|||
fi
|
||||
|
||||
new "restconf get restconf/yang-library-version. RFC8040 3.3.3"
|
||||
expecteq "$(curl -sG http://localhost/restconf/yang-library-version)" 0 '{"yang-library-version": "2016-06-21"}'
|
||||
expecteq "$(curl -sG http://localhost/restconf/yang-library-version)" 0 '{"yang-library-version":"2016-06-21"}'
|
||||
|
||||
new "restconf get restconf/yang-library-version. RFC8040 3.3.3 (xml)"
|
||||
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/yang-library-version)
|
||||
|
|
@ -97,7 +97,7 @@ if [ -z "$match" ]; then
|
|||
fi
|
||||
|
||||
new "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895 (explicit resource)"
|
||||
expecteq "$(curl -s -H 'Accept: application/yang-data+json' -G http://localhost/restconf/data/ietf-yang-library:modules-state/module=ietf-interfaces,2018-02-20)" 0 '{"ietf-yang-library:module": [{"name": "ietf-interfaces","revision": "2018-02-20","namespace": "urn:ietf:params:xml:ns:yang:ietf-interfaces","conformance-type": "implement"}]}
|
||||
expecteq "$(curl -s -H 'Accept: application/yang-data+json' -G http://localhost/restconf/data/ietf-yang-library:modules-state/module=ietf-interfaces,2018-02-20)" 0 '{"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"
|
||||
|
|
@ -111,14 +111,14 @@ new "restconf empty rpc"
|
|||
expecteq "$(curl -s -X POST -d {\"clixon-example:input\":null} http://localhost/restconf/operations/clixon-example:empty)" 0 ""
|
||||
|
||||
new "restconf empty rpc with extra args (should fail)"
|
||||
expecteq "$(curl -s -X POST -d {\"clixon-example:input\":{\"extra\":null}} http://localhost/restconf/operations/clixon-example:empty)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}}
'
|
||||
expecteq "$(curl -s -X POST -d {\"clixon-example:input\":{\"extra\":null}} http://localhost/restconf/operations/clixon-example:empty)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error"}}}
'
|
||||
|
||||
new "restconf get empty config + state json"
|
||||
expecteq "$(curl -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state": {"op": ["42","41","43"]}}
|
||||
expecteq "$(curl -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["42","41","43"]}}
|
||||
'
|
||||
|
||||
new "restconf get empty config + state json with wrong module name"
|
||||
expecteq "$(curl -sSG http://localhost/restconf/data/badmodule:state)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "No such yang module: badmodule"}}}
'
|
||||
expecteq "$(curl -sSG http://localhost/restconf/data/badmodule:state)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","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)
|
||||
|
|
@ -129,7 +129,7 @@ if [ -z "$match" ]; then
|
|||
fi
|
||||
|
||||
new "restconf get data/ json"
|
||||
expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op": ["42","41","43"]}
|
||||
expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":["42","41","43"]}
|
||||
'
|
||||
|
||||
new "restconf get state operation"
|
||||
|
|
@ -142,7 +142,7 @@ 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","41","43"]}
|
||||
expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":["42","41","43"]}
|
||||
'
|
||||
|
||||
new "restconf get state operation type xml"
|
||||
|
|
@ -155,7 +155,7 @@ 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": ["42","41","43"]}}
|
||||
expecteq "$(curl -s -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["42","41","43"]}}
|
||||
'
|
||||
|
||||
# Exact match
|
||||
|
|
@ -163,13 +163,13 @@ new "restconf Add subtree eth/0/0 to datastore using POST"
|
|||
expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 'HTTP/1.1 200 OK'
|
||||
|
||||
new "restconf Re-add subtree eth/0/0 which should give error"
|
||||
expectfn 'curl -s -X POST -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}'
|
||||
expectfn 'curl -s -X POST -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}'
|
||||
|
||||
# XXX Cant get this to work
|
||||
#expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"ex:eth\",\"enabled\":true}}} http://localhost/restconf/data)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}'
|
||||
#expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"ex:eth\",\"enabled\":true}}} http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}'
|
||||
|
||||
new "restconf Check interfaces eth/0/0 added"
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data/ietf-interfaces:interfaces" 0 '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true,"oper-status": "up"}]}}}
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data/ietf-interfaces:interfaces" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"ex:eth","enabled":true,"oper-status":"up"}]}}}
|
||||
'
|
||||
|
||||
new "restconf delete interfaces"
|
||||
|
|
@ -187,60 +187,60 @@ expectfn 'curl -s -X POST -d {"ietf-interfaces:interface":{"name":"eth/0/0","typ
|
|||
#expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type\":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces)" 0 ""
|
||||
|
||||
new "restconf Check eth/0/0 added config"
|
||||
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true,"oper-status": "up"}]}}
|
||||
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"ex:eth","enabled":true,"oper-status":"up"}]}}
|
||||
'
|
||||
|
||||
new "restconf Check eth/0/0 added state"
|
||||
expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state": {"op": ["42","41","43"]}}
|
||||
expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["42","41","43"]}}
|
||||
'
|
||||
|
||||
new "restconf Re-post eth/0/0 which should generate error"
|
||||
expecteq "$(curl -s -X POST -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex: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"}}}
'
|
||||
expecteq "$(curl -s -X POST -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex: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"}}}
'
|
||||
|
||||
new "Add leaf description using POST"
|
||||
expecteq "$(curl -s -X POST -d '{"ietf-interfaces:description":"The-first-interface"}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 ""
|
||||
|
||||
new "Add nothing using POST"
|
||||
expectfn 'curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": " on line 1: syntax error at or before:'
|
||||
expectfn 'curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":" on line 1: syntax error at or before:'
|
||||
|
||||
new "restconf Check description added"
|
||||
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "ex:eth","enabled": true,"oper-status": "up"}]}}
|
||||
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","description":"The-first-interface","type":"ex:eth","enabled":true,"oper-status":"up"}]}}
|
||||
'
|
||||
|
||||
new "restconf delete eth/0/0"
|
||||
expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 ""
|
||||
|
||||
new "Check deleted eth/0/0"
|
||||
expectfn 'curl -s -G http://localhost/restconf/data' 0 $state
|
||||
expectfn 'curl -s -G 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 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"}}}
'
|
||||
|
||||
new "restconf Add subtree eth/0/0 using PUT"
|
||||
expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 ""
|
||||
|
||||
new "restconf get subtree"
|
||||
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true,"oper-status": "up"}]}}
|
||||
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"ex:eth","enabled":true,"oper-status":"up"}]}}
|
||||
'
|
||||
|
||||
new "restconf rpc using POST json"
|
||||
expecteq "$(curl -s -X POST -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 -d '{"clixon-example:input":{"x":42}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"42","y":"42"}}
|
||||
'
|
||||
|
||||
new "restconf rpc using POST json wrong"
|
||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "wrongelement"},"error-severity": "error"}}}
'
|
||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error"}}}
'
|
||||
|
||||
new "restconf rpc non-existing rpc without namespace"
|
||||
expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "kalle"},"error-severity": "error","error-message": "RPC not defined"}}}
'
|
||||
expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" 0 '{"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"
|
||||
expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/clixon-example:kalle)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "kalle"},"error-severity": "error","error-message": "RPC not defined"}}}
'
|
||||
expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/clixon-example:kalle)" 0 '{"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"
|
||||
expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "Operation name expected"}}}
'
|
||||
expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"Operation name expected"}}}
'
|
||||
|
||||
new "restconf rpc missing input"
|
||||
expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": "restconf RPC does not have input statement"}}}
'
|
||||
expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"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 "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":42}}' http://localhost/restconf/operations/clixon-example:example)
|
||||
|
|
@ -251,7 +251,7 @@ if [ -z "$match" ]; then
|
|||
fi
|
||||
|
||||
new "restconf rpc using wrong prefix"
|
||||
expecteq "$(curl -s -X POST -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 -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"}}}
'
|
||||
|
||||
new "restconf local client rpc using POST xml"
|
||||
ret=$(curl -s -i -X POST -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":"example"}}' http://localhost/restconf/operations/clixon-example:client-rpc)
|
||||
|
|
@ -262,10 +262,10 @@ if [ -z "$match" ]; then
|
|||
fi
|
||||
|
||||
new "restconf Add subtree without key (expected error)"
|
||||
expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": "malformed key, expected '"'"'=restval'"'"'"}}}
'
|
||||
expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key, expected '"'"'=restval'"'"'"}}}
'
|
||||
|
||||
new "restconf Add subtree with too many keys (expected error)"
|
||||
expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=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 -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex: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"}}}
'
|
||||
|
||||
new "Kill restconf daemon"
|
||||
stop_restconf
|
||||
|
|
|
|||
|
|
@ -90,19 +90,19 @@ wait_backend
|
|||
wait_restconf
|
||||
|
||||
new "restconf POST tree without key"
|
||||
expectfn 'curl -s -X POST -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"}}}
'
|
||||
expectfn 'curl -s -X POST -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"}}}
'
|
||||
|
||||
new "restconf POST initial tree"
|
||||
expectfn 'curl -s -X POST -d {"example:cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 ""
|
||||
|
||||
new "restconf POST top without namespace"
|
||||
expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "cont1"},"error-severity": "error","error-message": "Unassigned yang spec"}}}'
|
||||
expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"cont1"},"error-severity":"error","error-message":"Unassigned yang spec"}}}'
|
||||
|
||||
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 http://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 http://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)
|
||||
|
|
@ -113,55 +113,55 @@ 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 http://localhost/restconf/data/example:cont1/interface=local0/type" 0 '{"example:type":"regular"}'
|
||||
|
||||
new "restconf POST interface without mandatory type"
|
||||
expectfn 'curl -s -X POST 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"}}}
'
|
||||
expectfn 'curl -s -X POST 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"}}}
'
|
||||
|
||||
new "restconf POST interface without mandatory key"
|
||||
expectfn 'curl -s -X POST 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"}}}
'
|
||||
expectfn 'curl -s -X POST 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"}}}
'
|
||||
|
||||
new "restconf POST interface"
|
||||
expectfn 'curl -s -X POST -d {"example:interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/example:cont1' 0 ""
|
||||
|
||||
new "restconf POST interface without namespace"
|
||||
expectfn 'curl -s -X POST -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": "Data is not prefixed with matching namespace"}}}'
|
||||
expectfn 'curl -s -X POST -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":"Data is not prefixed with matching namespace"}}}'
|
||||
|
||||
new "restconf POST again"
|
||||
expecteq "$(curl -s -X POST -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 -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"}}}
'
|
||||
|
||||
new "restconf POST from top"
|
||||
expecteq "$(curl -s -X POST -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 -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"}}}
'
|
||||
|
||||
new "restconf DELETE"
|
||||
expectfn 'curl -s -X DELETE http://localhost/restconf/data/example:cont1' 0 ""
|
||||
|
||||
new "restconf GET null datastore"
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-type": "application","error-tag": "invalid-value","error-severity": "error","error-message": "Instance does not exist"}}}}'
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors":{"error":{"rpc-error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}}'
|
||||
|
||||
new "restconf POST initial tree"
|
||||
expectfn 'curl -s -X POST -d {"example:cont1":{"interface":{"name":"local0","type":"regular"}}} http://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 http://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 ""
|
||||
|
||||
new "restconf GET null datastore"
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-type": "application","error-tag": "invalid-value","error-severity": "error","error-message": "Instance does not exist"}}}}'
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors":{"error":{"rpc-error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}}'
|
||||
|
||||
new "restconf PUT initial datastore"
|
||||
expectfn 'curl -s -X PUT -d {"data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 ""
|
||||
|
||||
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 http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}'
|
||||
|
||||
new "restconf PUT replace datastore"
|
||||
expectfn 'curl -s -X PUT -d {"data":{"example:cont2":{"name":"foo"}}} http://localhost/restconf/data' 0 ""
|
||||
|
||||
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 http://localhost/restconf/data/example:cont2" 0 '{"example:cont2":{"name":"foo"}}'
|
||||
|
||||
new "restconf PUT initial datastore again"
|
||||
expectfn 'curl -s -X PUT -d {"data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 ""
|
||||
|
|
@ -170,26 +170,26 @@ new "restconf PUT change interface"
|
|||
expectfn 'curl -s -X PUT -d {"example:interface":{"name":"local0","type":"atm0"}} http://localhost/restconf/data/example:cont1/interface=local0' 0 ""
|
||||
|
||||
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 http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"atm0"}\]}}'
|
||||
|
||||
new "restconf PUT add interface"
|
||||
expectfn 'curl -s -X PUT -d {"example:interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/example:cont1/interface=TEST' 0 ""
|
||||
|
||||
new "restconf PUT change key error"
|
||||
expectfn 'curl -is -X PUT -d {"example:interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/example:cont1/interface=TEST' 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"}}}'
|
||||
expectfn 'curl -is -X PUT -d {"example:interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/example:cont1/interface=TEST' 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 change type to eth0 (non-key sub-element to list)"
|
||||
expectfn 'curl -s -X PUT -d {"example:type":"eth0"} http://localhost/restconf/data/example:cont1/interface=local0/type' 0 ""
|
||||
|
||||
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 http://localhost/restconf/data/example:cont1/interface=local0" 0 '{"example:interface":\[{"name":"local0","type":"eth0"}\]}'
|
||||
|
||||
#--------------- json type tests
|
||||
new "restconf POST type x3"
|
||||
expectfn 'curl -s -X POST -d {"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}} http://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 http://localhost/restconf/data/example:types' 0 '{"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}}'
|
||||
|
||||
new "Kill restconf daemon"
|
||||
stop_restconf
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ new "restconf GET non-existent container header"
|
|||
expectfn "curl -s -I -X GET http://localhost/restconf/data/example:a/c" 0 "HTTP/1.1 404 Not Found"
|
||||
|
||||
new "restconf GET non-existent container body"
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:a/c" 0 '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-type": "application","error-tag": "invalid-value","error-severity": "error","error-message": "Instance does not exist"}}}}'
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:a/c" 0 '{"ietf-restconf:errors":{"error":{"rpc-error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}}'
|
||||
|
||||
new "Kill restconf daemon"
|
||||
stop_restconf
|
||||
|
|
|
|||
364
test/test_restconf_jukebox.sh
Executable file
364
test/test_restconf_jukebox.sh
Executable file
|
|
@ -0,0 +1,364 @@
|
|||
#!/bin/bash
|
||||
# Restconf RFC8040 Appendix A and B "jukebox" example
|
||||
|
||||
# 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
|
||||
|
||||
cfg=$dir/conf.xml
|
||||
fyang=$dir/restconf.yang
|
||||
fxml=$dir/initial.xml
|
||||
|
||||
# <CLICON_YANG_MODULE_MAIN>example</CLICON_YANG_MODULE_MAIN>
|
||||
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>$IETFRFC</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
|
||||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
|
||||
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
|
||||
<CLICON_BACKEND_PIDFILE>$dir/restconf.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||
<CLICON_STREAM_DISCOVERY_RFC8040>true</CLICON_STREAM_DISCOVERY_RFC8040>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
cat <<EOF > $fyang
|
||||
module example-jukebox {
|
||||
|
||||
namespace "http://example.com/ns/example-jukebox";
|
||||
prefix "jbox";
|
||||
|
||||
organization "Example, Inc.";
|
||||
contact "support at example.com";
|
||||
description "Example Jukebox Data Model Module.";
|
||||
revision "2016-08-15" {
|
||||
description "Initial version.";
|
||||
reference "example.com document 1-4673.";
|
||||
}
|
||||
|
||||
identity genre {
|
||||
description
|
||||
"Base for all genre types.";
|
||||
}
|
||||
|
||||
// abbreviated list of genre classifications
|
||||
identity alternative {
|
||||
base genre;
|
||||
description
|
||||
"Alternative music.";
|
||||
}
|
||||
identity blues {
|
||||
base genre;
|
||||
description
|
||||
"Blues music.";
|
||||
}
|
||||
identity country {
|
||||
base genre;
|
||||
description
|
||||
"Country music.";
|
||||
}
|
||||
identity jazz {
|
||||
base genre;
|
||||
description
|
||||
"Jazz music.";
|
||||
}
|
||||
identity pop {
|
||||
base genre;
|
||||
description
|
||||
"Pop music.";
|
||||
}
|
||||
identity rock {
|
||||
base genre;
|
||||
description
|
||||
"Rock music.";
|
||||
}
|
||||
|
||||
container jukebox {
|
||||
presence
|
||||
"An empty container indicates that the jukebox
|
||||
service is available.";
|
||||
|
||||
description
|
||||
"Represents a 'jukebox' resource, with a library, playlists,
|
||||
and a 'play' operation.";
|
||||
|
||||
container library {
|
||||
|
||||
description
|
||||
"Represents the 'jukebox' library resource.";
|
||||
|
||||
list artist {
|
||||
key name;
|
||||
description
|
||||
"Represents one 'artist' resource within the
|
||||
'jukebox' library resource.";
|
||||
|
||||
leaf name {
|
||||
type string {
|
||||
length "1 .. max";
|
||||
}
|
||||
description
|
||||
"The name of the artist.";
|
||||
}
|
||||
|
||||
list album {
|
||||
key name;
|
||||
description
|
||||
"Represents one 'album' resource within one
|
||||
'artist' resource, within the jukebox library.";
|
||||
|
||||
leaf name {
|
||||
type string {
|
||||
length "1 .. max";
|
||||
}
|
||||
description
|
||||
"The name of the album.";
|
||||
}
|
||||
|
||||
leaf genre {
|
||||
type identityref { base genre; }
|
||||
description
|
||||
"The genre identifying the type of music on
|
||||
the album.";
|
||||
}
|
||||
|
||||
leaf year {
|
||||
type uint16 {
|
||||
range "1900 .. max";
|
||||
}
|
||||
description
|
||||
"The year the album was released.";
|
||||
}
|
||||
|
||||
container admin {
|
||||
description
|
||||
"Administrative information for the album.";
|
||||
|
||||
leaf label {
|
||||
type string;
|
||||
description
|
||||
"The label that released the album.";
|
||||
}
|
||||
leaf catalogue-number {
|
||||
type string;
|
||||
description
|
||||
"The album's catalogue number.";
|
||||
}
|
||||
}
|
||||
|
||||
list song {
|
||||
key name;
|
||||
description
|
||||
"Represents one 'song' resource within one
|
||||
'album' resource, within the jukebox library.";
|
||||
|
||||
leaf name {
|
||||
type string {
|
||||
length "1 .. max";
|
||||
}
|
||||
description
|
||||
"The name of the song.";
|
||||
}
|
||||
|
||||
leaf location {
|
||||
type string;
|
||||
mandatory true;
|
||||
description
|
||||
"The file location string of the
|
||||
media file for the song.";
|
||||
}
|
||||
leaf format {
|
||||
type string;
|
||||
description
|
||||
"An identifier string for the media type
|
||||
for the file associated with the
|
||||
'location' leaf for this entry.";
|
||||
}
|
||||
leaf length {
|
||||
type uint32;
|
||||
units "seconds";
|
||||
description
|
||||
"The duration of this song in seconds.";
|
||||
}
|
||||
} // end list 'song'
|
||||
} // end list 'album'
|
||||
} // end list 'artist'
|
||||
|
||||
leaf artist-count {
|
||||
type uint32;
|
||||
units "artists";
|
||||
config false;
|
||||
description
|
||||
"Number of artists in the library.";
|
||||
}
|
||||
leaf album-count {
|
||||
type uint32;
|
||||
units "albums";
|
||||
config false;
|
||||
description
|
||||
"Number of albums in the library.";
|
||||
}
|
||||
leaf song-count {
|
||||
type uint32;
|
||||
units "songs";
|
||||
config false;
|
||||
description
|
||||
"Number of songs in the library.";
|
||||
}
|
||||
} // end library
|
||||
|
||||
list playlist {
|
||||
key name;
|
||||
description
|
||||
"Example configuration data resource.";
|
||||
|
||||
leaf name {
|
||||
type string;
|
||||
description
|
||||
"The name of the playlist.";
|
||||
}
|
||||
leaf description {
|
||||
type string;
|
||||
description
|
||||
"A comment describing the playlist.";
|
||||
}
|
||||
list song {
|
||||
key index;
|
||||
ordered-by user;
|
||||
|
||||
description
|
||||
"Example nested configuration data resource.";
|
||||
|
||||
leaf index { // not really needed
|
||||
type uint32;
|
||||
description
|
||||
"An arbitrary integer index for this playlist song.";
|
||||
}
|
||||
leaf id {
|
||||
type instance-identifier;
|
||||
mandatory true;
|
||||
description
|
||||
"Song identifier. Must identify an instance of
|
||||
/jukebox/library/artist/album/song/name.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
container player {
|
||||
description
|
||||
"Represents the jukebox player resource.";
|
||||
|
||||
leaf gap {
|
||||
type decimal64 {
|
||||
fraction-digits 1;
|
||||
range "0.0 .. 2.0";
|
||||
}
|
||||
units "tenths of seconds";
|
||||
description
|
||||
"Time gap between each song.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rpc play {
|
||||
description
|
||||
"Control function for the jukebox player.";
|
||||
input {
|
||||
leaf playlist {
|
||||
type string;
|
||||
mandatory true;
|
||||
description
|
||||
"The playlist name.";
|
||||
}
|
||||
leaf song-number {
|
||||
type uint32;
|
||||
mandatory true;
|
||||
description
|
||||
"Song number in playlist to play.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
new "test params: -f $cfg"
|
||||
|
||||
if [ $BE -ne 0 ]; then
|
||||
new "kill old backend"
|
||||
sudo clixon_backend -zf $cfg
|
||||
if [ $? -ne 0 ]; then
|
||||
err
|
||||
fi
|
||||
sudo pkill clixon_backend # to be sure
|
||||
new "start backend -s init -f $cfg"
|
||||
start_backend -s init -f $cfg
|
||||
fi
|
||||
|
||||
new "kill old restconf daemon"
|
||||
sudo pkill -u www-data -f "/www-data/clixon_restconf"
|
||||
|
||||
new "start restconf daemon"
|
||||
start_restconf -f $cfg
|
||||
|
||||
new "waiting"
|
||||
wait_backend
|
||||
wait_restconf
|
||||
|
||||
new "B.1.1. Retrieve the Top-Level API Resource root"
|
||||
expectpart "$(curl -s -i -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>"
|
||||
|
||||
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 -s -i -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"
|
||||
|
||||
new "B.1.1. Retrieve the Top-Level API Resource /restconf xml (not in RFC)"
|
||||
expectpart "$(curl -s -i -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>'
|
||||
|
||||
# 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 -s -i -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":' '"module":\[{"name":"example-jukebox","revision":"2016-08-15","namespace":"http://example.com/ns/example-jukebox","conformance-type":"implement"}'
|
||||
|
||||
new "B.1.3. Retrieve the Server Capability Information"
|
||||
expectpart "$(curl -s -i -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></capabilities>'
|
||||
|
||||
if false; then # NYI
|
||||
new "B.2.1. Create New Data Resources"
|
||||
expectpart "$(curl -s -i -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:" # etc
|
||||
|
||||
new "B.2.2. Detect Datastore Resource Entity-Tag Change"
|
||||
new "B.2.3. Edit a Datastore Resource"
|
||||
new "B.2.4. Replace a Datastore Resource"
|
||||
new "B.2.5. Edit a Data Resource"
|
||||
new 'B.3.1. "content" Parameter'
|
||||
new 'B.3.2. "depth" Parameter'
|
||||
new 'B.3.3. "fields" Parameter'
|
||||
new 'B.3.4. "insert" Parameter'
|
||||
new 'B.3.5. "point" Parameter'
|
||||
new 'B.3.6. "filter" Parameter'
|
||||
new 'B.3.7. "start-time" Parameter'
|
||||
new 'B.3.8. "stop-time" Parameter'
|
||||
new 'B.3.9. "with-defaults" Parameter'
|
||||
fi
|
||||
|
||||
new "Kill restconf daemon"
|
||||
stop_restconf
|
||||
|
||||
if [ $BE -eq 0 ]; then
|
||||
exit # BE
|
||||
fi
|
||||
|
||||
new "Kill backend"
|
||||
# Check if premature kill
|
||||
pid=`pgrep -u root -f clixon_backend`
|
||||
if [ -z "$pid" ]; then
|
||||
err "backend already dead"
|
||||
fi
|
||||
# kill backend
|
||||
stop_backend -f $cfg
|
||||
|
||||
rm -rf $dir
|
||||
|
|
@ -98,16 +98,16 @@ new "restconf PUT change whole list entry (same keys)"
|
|||
expectfn 'curl -s -X PUT http://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)"
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y -d {"a":{"b":"x","c":"y","nonkey":"z"}}' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": "Data is not prefixed with matching namespace"}}}'
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y -d {"a":{"b":"x","c":"y","nonkey":"z"}}' 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Data is not prefixed with matching namespace"}}}'
|
||||
|
||||
new "restconf PUT change list entry (wrong keys)(expect fail)"
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y -d {"list:a":{"b":"y","c":"x"}}' 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"}}}'
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y -d {"list:a":{"b":"y","c":"x"}}' 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 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>
'
|
||||
|
||||
new "restconf PUT change list entry (just one key)(expect fail)"
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x -d {"list:a":{"b":"x"}}' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": "List key a length mismatch"}}}
'
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x -d {"list:a":{"b":"x"}}' 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 sub non-key"
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/nonkey -d {"list:nonkey":"u"}' 0 ''
|
||||
|
|
@ -116,37 +116,37 @@ new "restconf PUT sub key same value"
|
|||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/b -d {"list:b":"x"}' 0 ''
|
||||
|
||||
new "restconf PUT just key other value (should fail)ZX"
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x/b -d {"b":"y"}' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": "List key a length mismatch"}}}'
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x/b -d {"b":"y"}' 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 add leaf-list entry"
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/d=x -d {"list:d":"x"}' 0 ''
|
||||
|
||||
new "restconf PUT change leaf-list entry (expect fail)"
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/d=x -d {"list:d":"y"}' 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"}}}'
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/d=x -d {"list:d":"y"}' 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"
|
||||
expectfn 'curl -s -X PUT http://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)"
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/e=z -d {"list:e":{"f":"wrong","nonley":"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"}}}'
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/e=z -d {"list:e":{"f":"wrong","nonley":"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"
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/e=z/nonkey -d {"list:nonkey":"u"}' 0 ''
|
||||
|
||||
new "restconf PUT list-list single first key"
|
||||
expectfn 'curl -s -X PUT 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"}}}'
|
||||
expectfn 'curl -s -X PUT 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"}}}'
|
||||
|
||||
new "restconf PUT list-list just key ok"
|
||||
expectfn 'curl -s -X PUT http://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)"
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/e=z/f -d {"list:f":"wrong"}' 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"}}}'
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/e=z/f -d {"list:f":"wrong"}' 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 add list+leaf-list entry"
|
||||
expectfn 'curl -s -X PUT http://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)"
|
||||
expectfn 'curl -s -X PUT 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"}}}'
|
||||
expectfn 'curl -s -X PUT 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"}}}'
|
||||
|
||||
|
||||
new "Kill restconf daemon"
|
||||
|
|
|
|||
|
|
@ -70,20 +70,20 @@ new "netconf empty rpc"
|
|||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><empty xmlns="urn:example:clixon"/></rpc>]]>]]>' '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><ok/></rpc-reply>]]>]]>$'
|
||||
|
||||
new "restconf example rpc json/json default - no http media headers"
|
||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"x":0}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output": {"x": "0","y": "42"}}
|
||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"x":0}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"0","y":"42"}}
|
||||
'
|
||||
|
||||
new "restconf example rpc json/json change y default"
|
||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"x":"0","y":"99"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output": {"x": "0","y": "99"}}
|
||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"x":"0","y":"99"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"0","y":"99"}}
|
||||
'
|
||||
|
||||
new "restconf example rpc json/json"
|
||||
# XXX example:input example:output
|
||||
expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+json' -H 'Accept: application/yang-data+json' -d '{"clixon-example:input":{"x":"0"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output": {"x": "0","y": "42"}}
|
||||
expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+json' -H 'Accept: application/yang-data+json' -d '{"clixon-example:input":{"x":"0"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"0","y":"42"}}
|
||||
'
|
||||
|
||||
new "restconf example rpc xml/json"
|
||||
expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+json' -d '<input xmlns="urn:example:clixon"><x>0</x></input>' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output": {"x": "0","y": "42"}}
|
||||
expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+json' -d '<input xmlns="urn:example:clixon"><x>0</x></input>' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"0","y":"42"}}
|
||||
'
|
||||
|
||||
new "restconf example rpc json/xml"
|
||||
|
|
@ -115,7 +115,7 @@ if [ -z "$match" ]; then
|
|||
fi
|
||||
|
||||
new "restconf empty rpc with input x"
|
||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"x":0}}' http://localhost/restconf/operations/clixon-example:empty)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "x"},"error-severity": "error"}}}
'
|
||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"x":0}}' http://localhost/restconf/operations/clixon-example:empty)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"x"},"error-severity":"error"}}}
'
|
||||
|
||||
# cornercase: optional has yang input/output sections but test without body
|
||||
new "restconf optional rpc with null input and output"
|
||||
|
|
@ -127,16 +127,16 @@ if [ -z "$match" ]; then
|
|||
fi
|
||||
|
||||
new "restconf omit mandatory"
|
||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":null}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "x"},"error-severity": "error","error-message": "Mandatory variable"}}}
'
|
||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":null}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"x"},"error-severity":"error","error-message":"Mandatory variable"}}}
'
|
||||
|
||||
new "restconf add extra"
|
||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"x":"0","extra":"0"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}}
'
|
||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"x":"0","extra":"0"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error"}}}
'
|
||||
|
||||
new "restconf wrong method"
|
||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"x":"0"}}' http://localhost/restconf/operations/clixon-example:wrong)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "wrong"},"error-severity": "error","error-message": "RPC not defined"}}}
'
|
||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"x":"0"}}' http://localhost/restconf/operations/clixon-example:wrong)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"wrong"},"error-severity":"error","error-message":"RPC not defined"}}}
'
|
||||
|
||||
new "restconf example missing input"
|
||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":null}' http://localhost/restconf/operations/ietf-netconf:edit-config)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "target"},"error-severity": "error","error-message": "Mandatory variable"}}}
'
|
||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":null}' http://localhost/restconf/operations/ietf-netconf:edit-config)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"target"},"error-severity":"error","error-message":"Mandatory variable"}}}
'
|
||||
|
||||
new "netconf kill-session missing session-id mandatory"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><kill-session/></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>missing-element</error-tag><error-info><bad-element>session-id</bad-element></error-info><error-severity>error</error-severity><error-message>Mandatory variable</error-message></rpc-error></rpc-reply>]]>]]>$'
|
||||
|
|
|
|||
|
|
@ -165,11 +165,11 @@ sleep 2
|
|||
new "2. Restconf RFC8040 stream testing"
|
||||
# 2.1 Stream discovery
|
||||
new "restconf event stream discovery RFC8040 Sec 6.2"
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams" 0 '{"ietf-restconf-monitoring:streams": {"stream": \[{"name": "EXAMPLE","description": "Example event stream","replay-support": true,"access": \[{"encoding": "xml","location": "https://localhost/streams/EXAMPLE"}\]}\]}'
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams" 0 '{"ietf-restconf-monitoring:streams":{"stream":\[{"name":"EXAMPLE","description":"Example event stream","replay-support":true,"access":\[{"encoding":"xml","location":"https://localhost/streams/EXAMPLE"}\]}\]}'
|
||||
|
||||
sleep 2
|
||||
new "restconf subscribe RFC8040 Sec 6.3, get location"
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams/stream=EXAMPLE/access=xml/location" 0 '{"ietf-restconf-monitoring:location": "https://localhost/streams/EXAMPLE"}'
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams/stream=EXAMPLE/access=xml/location" 0 '{"ietf-restconf-monitoring:location":"https://localhost/streams/EXAMPLE"}'
|
||||
|
||||
sleep 2
|
||||
# Restconf stream subscription RFC8040 Sec 6.3
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ new "restconf edit sub2"
|
|||
expectfn 'curl -s -i -X POST http://localhost/restconf/data -d {"main:sub2":{"x":"foo","ext2":"foo"}}' 0 'HTTP/1.1 200 OK'
|
||||
|
||||
new "restconf check main/sub1/sub2 contents"
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": {"main:main": {"ext": "foo","x": "foo"},"main:sub1": {"ext1": "foo","x": "foo"},"main:sub2": {"ext2": "foo","x": "foo"}}}'
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data":{"main:main":{"ext":"foo","x":"foo"},"main:sub1":{"ext1":"foo","x":"foo"},"main:sub2":{"ext2":"foo","x":"foo"}}}'
|
||||
|
||||
new "Kill restconf daemon"
|
||||
stop_restconf
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ new "xml parse"
|
|||
expecteof "$clixon_util_xml" 0 "<a><b/></a>" "^<a><b/></a>$"
|
||||
|
||||
new "xml parse to json"
|
||||
expecteof "$clixon_util_xml -j" 0 "<a><b/></a>" '{"a": {"b": null}}'
|
||||
expecteof "$clixon_util_xml -j" 0 "<a><b/></a>" '{"a":{"b":null}}'
|
||||
|
||||
new "xml parse strange names"
|
||||
expecteof "$clixon_util_xml" 0 "<_-><b0.><c-.-._/></b0.></_->" "<_-><b0.><c-.-._/></b0.></_->"
|
||||
|
|
@ -38,7 +38,7 @@ new "xml simple CDATA"
|
|||
expecteofx "$clixon_util_xml" 0 '<a><![CDATA[a text]]></a>' '<a><![CDATA[a text]]></a>'
|
||||
|
||||
new "xml simple CDATA to json"
|
||||
expecteofx "$clixon_util_xml -j" 0 '<a><![CDATA[a text]]></a>' '{"a": "a text"}'
|
||||
expecteofx "$clixon_util_xml -j" 0 '<a><![CDATA[a text]]></a>' '{"a":"a text"}'
|
||||
|
||||
new "xml complex CDATA"
|
||||
XML=$(cat <<EOF
|
||||
|
|
@ -60,7 +60,7 @@ expecteof "$clixon_util_xml" 0 "$XML" "^<a><description>An example of escaped CE
|
|||
</sometext><data><![CDATA[This text contains a CEND ]]]]><![CDATA[>]]></data><alternative><![CDATA[This text contains a CEND ]]]><![CDATA[]>]]></alternative></a>$"
|
||||
|
||||
JSON=$(cat <<EOF
|
||||
{"a": {"description": "An example of escaped CENDs","sometext": " They're saying \"x < y\" & that \"z > y\" so I guess that means that z > x ","data": "This text contains a CEND ]]>","alternative": "This text contains a CEND ]]>"}}
|
||||
{"a":{"description":"An example of escaped CENDs","sometext":" They're saying \"x < y\" & that \"z > y\" so I guess that means that z > x ","data":"This text contains a CEND ]]>","alternative":"This text contains a CEND ]]>"}}
|
||||
EOF
|
||||
)
|
||||
new "xml complex CDATA to json"
|
||||
|
|
@ -74,7 +74,7 @@ new "xml encode <>&"
|
|||
expecteof "$clixon_util_xml" 0 "$XML" "$XML"
|
||||
|
||||
new "xml encode <>& to json"
|
||||
expecteof "$clixon_util_xml -j" 0 "$XML" '{"message": "Less than: < , greater than: > ampersand: & "}'
|
||||
expecteof "$clixon_util_xml -j" 0 "$XML" '{"message":"Less than: < , greater than: > ampersand: & "}'
|
||||
|
||||
XML=$(cat <<EOF
|
||||
<message>single-quote character ' represented as ' and double-quote character as "</message>
|
||||
|
|
@ -84,7 +84,7 @@ new "xml single and double quote"
|
|||
expecteof "$clixon_util_xml" 0 "$XML" "<message>single-quote character ' represented as ' and double-quote character as \"</message>"
|
||||
|
||||
JSON=$(cat <<EOF
|
||||
{"message": "single-quote character ' represented as ' and double-quote character as \""}
|
||||
{"message":"single-quote character ' represented as ' and double-quote character as \""}
|
||||
EOF
|
||||
)
|
||||
new "xml single and double quotes to json"
|
||||
|
|
@ -94,7 +94,7 @@ new "xml backspace"
|
|||
expecteofx "$clixon_util_xml" 0 "<a>a\b</a>" "<a>a\b</a>"
|
||||
|
||||
new "xml backspace to json"
|
||||
expecteofx "$clixon_util_xml -j" 0 "<a>a\b</a>" '{"a": "a\\b"}'
|
||||
expecteofx "$clixon_util_xml -j" 0 "<a>a\b</a>" '{"a":"a\\b"}'
|
||||
|
||||
new "Double quotes for attributes"
|
||||
expecteof "$clixon_util_xml" 0 '<x a="t"/>' '<x a="t"/>'
|
||||
|
|
|
|||
126
test/test_yang_extension.sh
Executable file
126
test/test_yang_extension.sh
Executable file
|
|
@ -0,0 +1,126 @@
|
|||
#!/bin/bash
|
||||
# Yang extensions and unknown statements.
|
||||
# 1) First test syntax
|
||||
# Assuming the following extension definition:
|
||||
# prefix p;
|
||||
# extension keyw {
|
||||
# argument arg; # optional
|
||||
# }
|
||||
# there are four forms of unknown statement as follows:
|
||||
# p:keyw;
|
||||
# p:keyw arg;
|
||||
# p:keyw { stmt;* }
|
||||
# p:keyw arg { stmt;* }
|
||||
#
|
||||
# 2) The extensions results in in a node data definition.
|
||||
# Second, the example is run without the extension enabled, then it is enabled.
|
||||
|
||||
# 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
|
||||
|
||||
cfg=$dir/conf_yang.xml
|
||||
fyang=$dir/$APPNAME.yang
|
||||
|
||||
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_DIR>$IETFRFC</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
|
||||
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
|
||||
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
|
||||
<CLICON_NETCONF_DIR>/usr/local/lib/$APPNAME/netconf</CLICON_NETCONF_DIR>
|
||||
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
|
||||
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
|
||||
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
|
||||
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
|
||||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||
<CLICON_MODULE_LIBRARY_RFC7895>true</CLICON_MODULE_LIBRARY_RFC7895>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
cat <<EOF > $fyang
|
||||
module $APPNAME{
|
||||
yang-version 1.1;
|
||||
prefix ex;
|
||||
namespace "urn:example:clixon";
|
||||
extension e1 {
|
||||
description "no argument, no statements";
|
||||
}
|
||||
extension e2 {
|
||||
description "with argument, no statements";
|
||||
argument arg;
|
||||
}
|
||||
extension e3 {
|
||||
description "no argument, with statement";
|
||||
}
|
||||
extension e4 {
|
||||
description "with argument, with statement";
|
||||
argument arg;
|
||||
}
|
||||
grouping foo {
|
||||
leaf foo{
|
||||
type string;
|
||||
}
|
||||
}
|
||||
grouping bar {
|
||||
leaf bar{
|
||||
type string;
|
||||
}
|
||||
}
|
||||
|
||||
ex:e1;
|
||||
ex:e2 arg1;
|
||||
ex:e3 {
|
||||
uses foo;
|
||||
}
|
||||
ex:e4 arg1{
|
||||
uses bar;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
XML='<foo xmlns="urn:example:clixon">a string</foo>'
|
||||
|
||||
new "test params: -f $cfg"
|
||||
|
||||
if [ $BE -ne 0 ]; then
|
||||
new "kill old backend"
|
||||
sudo clixon_backend -zf $cfg
|
||||
if [ $? -ne 0 ]; then
|
||||
err
|
||||
fi
|
||||
new "start backend -s init -f $cfg"
|
||||
start_backend -s init -f $cfg
|
||||
|
||||
new "waiting"
|
||||
wait_backend
|
||||
fi
|
||||
|
||||
# The main example implements ex:e4
|
||||
new "Add extension foo (not implemented)"
|
||||
expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 '<rpc><edit-config><target><candidate/></target><config><foo xmlns="urn:example:clixon">a string</foo></config></edit-config></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>unknown-element</error-tag><error-info><bad-element>foo</bad-element></error-info><error-severity>error</error-severity>'
|
||||
|
||||
new "Add extension bar (is implemented)"
|
||||
expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 '<rpc><edit-config><target><candidate/></target><config><bar xmlns="urn:example:clixon">a string</bar></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>'
|
||||
|
||||
new "netconf get config"
|
||||
expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><bar xmlns="urn:example:clixon">a string</bar></data></rpc-reply>]]>]]>$'
|
||||
|
||||
if [ $BE -ne 0 ]; then
|
||||
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
|
||||
sudo pkill -u root -f clixon_backend
|
||||
fi
|
||||
|
||||
rm -rf $dir
|
||||
|
|
@ -105,7 +105,7 @@ new "restconf set x in example1"
|
|||
expecteq "$(curl -s -X POST -d '{"example1:x":42}' http://localhost/restconf/data)" 0 ''
|
||||
|
||||
new "restconf get config example1"
|
||||
expecteq "$(curl -s -X GET http://localhost/restconf/data/example1:x)" 0 '{"example1:x": 42}
|
||||
expecteq "$(curl -s -X GET http://localhost/restconf/data/example1:x)" 0 '{"example1:x":42}
|
||||
'
|
||||
|
||||
new "restconf set x in example2"
|
||||
|
|
@ -113,17 +113,17 @@ expecteq "$(curl -s -X POST -d '{"example2:x":{"y":99}}' http://localhost/restco
|
|||
|
||||
# XXX GET ../example1:x is translated to select=/x which gets both example1&2
|
||||
#new "restconf get config example1"
|
||||
#expecteq "$(curl -s -X GET http://localhost/restconf/data/example1:x)" 0 '{"example1:x": 42}
|
||||
#expecteq "$(curl -s -X GET http://localhost/restconf/data/example1:x)" 0 '{"example1:x":42}
|
||||
#
'
|
||||
|
||||
# XXX GET ../example2:x is translated to select=/x which gets both example1&2
|
||||
#new "restconf get config example2"
|
||||
#expecteq "$(curl -s -X GET http://localhost/restconf/data/example2:x)" 0 '{"example2:x": {"y":42}}
|
||||
#expecteq "$(curl -s -X GET http://localhost/restconf/data/example2:x)" 0 '{"example2:x":{"y":42}}
|
||||
#
'
|
||||
|
||||
new "restconf get config example1 and example2"
|
||||
ret=$(curl -s -X GET http://localhost/restconf/data)
|
||||
expect='"example1:x": 42,"example2:x": {"y": 99}'
|
||||
expect='"example1:x":42,"example2:x":{"y":99}'
|
||||
match=`echo $ret | grep -EZo "$expect"`
|
||||
if [ -z "$match" ]; then
|
||||
err "$expect" "$ret"
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ usage(char *argv0)
|
|||
"\t-D <level> \tDebug\n"
|
||||
"\t-j \t\tOutput as JSON\n"
|
||||
"\t-l <s|e|o> \tLog on (s)yslog, std(e)rr, std(o)ut (stderr is default)\n"
|
||||
"\t-p \t\tPretty-print output\n"
|
||||
"\t-y <filename> \tyang filename to parse (must be stand-alone)\n" ,
|
||||
argv0);
|
||||
exit(0);
|
||||
|
|
@ -96,10 +97,11 @@ main(int argc,
|
|||
yang_stmt *yspec = NULL;
|
||||
cxobj *xerr = NULL; /* malloced must be freed */
|
||||
int ret;
|
||||
int pretty = 0;
|
||||
|
||||
optind = 1;
|
||||
opterr = 0;
|
||||
while ((c = getopt(argc, argv, "hD:jl:y:")) != -1)
|
||||
while ((c = getopt(argc, argv, "hD:jl:py:")) != -1)
|
||||
switch (c) {
|
||||
case 'h':
|
||||
usage(argv[0]);
|
||||
|
|
@ -115,6 +117,9 @@ main(int argc,
|
|||
if ((logdst = clicon_log_opt(optarg[0])) < 0)
|
||||
usage(argv[0]);
|
||||
break;
|
||||
case 'p':
|
||||
pretty++;
|
||||
break;
|
||||
case 'y':
|
||||
yang_filename = optarg;
|
||||
break;
|
||||
|
|
@ -140,9 +145,9 @@ main(int argc,
|
|||
xc = NULL;
|
||||
while ((xc = xml_child_each(xt, xc, -1)) != NULL)
|
||||
if (json)
|
||||
xml2json_cbuf(cb, xc, 0); /* print xml */
|
||||
xml2json_cbuf(cb, xc, pretty); /* print xml */
|
||||
else
|
||||
clicon_xml2cbuf(cb, xc, 0, 0); /* print xml */
|
||||
clicon_xml2cbuf(cb, xc, 0, pretty); /* print xml */
|
||||
fprintf(stdout, "%s", cbuf_get(cb));
|
||||
fflush(stdout);
|
||||
retval = 0;
|
||||
|
|
|
|||
|
|
@ -523,9 +523,8 @@ module clixon-config {
|
|||
leaf CLICON_STREAM_DISCOVERY_RFC8040 {
|
||||
type boolean;
|
||||
default false;
|
||||
description "Enable event stream discovery as described in RFC 5277
|
||||
sections 3.2. If enabled, available streams will appear
|
||||
when doing netconf get or restconf GET";
|
||||
description
|
||||
"Enable monitoring information for the RESTCONF protocol from RFC 8040";
|
||||
}
|
||||
leaf CLICON_STREAM_PATH {
|
||||
type string;
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ YANGSPECS += ietf-routing@2018-03-13.yang
|
|||
YANGSPECS += ietf-yang-library@2016-06-21.yang
|
||||
YANGSPECS += ietf-netconf@2011-06-01.yang
|
||||
YANGSPECS += ietf-netconf-acm@2018-02-14.yang
|
||||
YANGSPECS += ietf-restconf@2017-01-26.yang
|
||||
YANGSPECS += ietf-restconf-monitoring@2017-01-26.yang
|
||||
YANGSPECS += ietf-netconf-monitoring@2010-10-04.yang
|
||||
|
||||
|
|
|
|||
278
yang/standard/ietf-restconf@2017-01-26.yang
Normal file
278
yang/standard/ietf-restconf@2017-01-26.yang
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
module ietf-restconf {
|
||||
yang-version 1.1;
|
||||
namespace "urn:ietf:params:xml:ns:yang:ietf-restconf";
|
||||
prefix "rc";
|
||||
|
||||
organization
|
||||
"IETF NETCONF (Network Configuration) Working Group";
|
||||
|
||||
contact
|
||||
"WG Web: <https://datatracker.ietf.org/wg/netconf/>
|
||||
WG List: <mailto:netconf@ietf.org>
|
||||
|
||||
Author: Andy Bierman
|
||||
<mailto:andy@yumaworks.com>
|
||||
|
||||
Author: Martin Bjorklund
|
||||
<mailto:mbj@tail-f.com>
|
||||
|
||||
Author: Kent Watsen
|
||||
<mailto:kwatsen@juniper.net>";
|
||||
|
||||
description
|
||||
"This module contains conceptual YANG specifications
|
||||
for basic RESTCONF media type definitions used in
|
||||
RESTCONF protocol messages.
|
||||
|
||||
Note that the YANG definitions within this module do not
|
||||
represent configuration data of any kind.
|
||||
The 'restconf-media-type' YANG extension statement
|
||||
provides a normative syntax for XML and JSON
|
||||
message-encoding purposes.
|
||||
|
||||
Copyright (c) 2017 IETF Trust and the persons identified as
|
||||
authors of the code. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or
|
||||
without modification, is permitted pursuant to, and subject
|
||||
to the license terms contained in, the Simplified BSD License
|
||||
set forth in Section 4.c of the IETF Trust's Legal Provisions
|
||||
Relating to IETF Documents
|
||||
(http://trustee.ietf.org/license-info).
|
||||
|
||||
This version of this YANG module is part of RFC 8040; see
|
||||
the RFC itself for full legal notices.";
|
||||
|
||||
revision 2017-01-26 {
|
||||
description
|
||||
"Initial revision.";
|
||||
reference
|
||||
"RFC 8040: RESTCONF Protocol.";
|
||||
}
|
||||
|
||||
extension yang-data {
|
||||
argument name {
|
||||
yin-element true;
|
||||
}
|
||||
description
|
||||
"This extension is used to specify a YANG data template that
|
||||
represents conceptual data defined in YANG. It is
|
||||
intended to describe hierarchical data independent of
|
||||
protocol context or specific message-encoding format.
|
||||
Data definition statements within a yang-data extension
|
||||
specify the generic syntax for the specific YANG data
|
||||
template, whose name is the argument of the 'yang-data'
|
||||
extension statement.
|
||||
|
||||
Note that this extension does not define a media type.
|
||||
A specification using this extension MUST specify the
|
||||
message-encoding rules, including the content media type.
|
||||
|
||||
The mandatory 'name' parameter value identifies the YANG
|
||||
data template that is being defined. It contains the
|
||||
template name.
|
||||
|
||||
This extension is ignored unless it appears as a top-level
|
||||
statement. It MUST contain data definition statements
|
||||
that result in exactly one container data node definition.
|
||||
An instance of a YANG data template can thus be translated
|
||||
into an XML instance document, whose top-level element
|
||||
corresponds to the top-level container.
|
||||
The module name and namespace values for the YANG module using
|
||||
the extension statement are assigned to instance document data
|
||||
conforming to the data definition statements within
|
||||
this extension.
|
||||
|
||||
The substatements of this extension MUST follow the
|
||||
'data-def-stmt' rule in the YANG ABNF.
|
||||
|
||||
The XPath document root is the extension statement itself,
|
||||
such that the child nodes of the document root are
|
||||
represented by the data-def-stmt substatements within
|
||||
this extension. This conceptual document is the context
|
||||
for the following YANG statements:
|
||||
|
||||
- must-stmt
|
||||
- when-stmt
|
||||
- path-stmt
|
||||
- min-elements-stmt
|
||||
- max-elements-stmt
|
||||
- mandatory-stmt
|
||||
- unique-stmt
|
||||
- ordered-by
|
||||
- instance-identifier data type
|
||||
|
||||
The following data-def-stmt substatements are constrained
|
||||
when used within a 'yang-data' extension statement.
|
||||
|
||||
- The list-stmt is not required to have a key-stmt defined.
|
||||
- The if-feature-stmt is ignored if present.
|
||||
- The config-stmt is ignored if present.
|
||||
- The available identity values for any 'identityref'
|
||||
leaf or leaf-list nodes are limited to the module
|
||||
containing this extension statement and the modules
|
||||
imported into that module.
|
||||
";
|
||||
}
|
||||
|
||||
rc:yang-data yang-errors {
|
||||
uses errors;
|
||||
}
|
||||
|
||||
rc:yang-data yang-api {
|
||||
uses restconf;
|
||||
}
|
||||
|
||||
grouping errors {
|
||||
description
|
||||
"A grouping that contains a YANG container
|
||||
representing the syntax and semantics of a
|
||||
YANG Patch error report within a response message.";
|
||||
|
||||
container errors {
|
||||
description
|
||||
"Represents an error report returned by the server if
|
||||
a request results in an error.";
|
||||
|
||||
list error {
|
||||
description
|
||||
"An entry containing information about one
|
||||
specific error that occurred while processing
|
||||
a RESTCONF request.";
|
||||
reference
|
||||
"RFC 6241, Section 4.3.";
|
||||
|
||||
leaf error-type {
|
||||
type enumeration {
|
||||
enum transport {
|
||||
description
|
||||
"The transport layer.";
|
||||
}
|
||||
enum rpc {
|
||||
description
|
||||
"The rpc or notification layer.";
|
||||
}
|
||||
enum protocol {
|
||||
description
|
||||
"The protocol operation layer.";
|
||||
}
|
||||
enum application {
|
||||
description
|
||||
"The server application layer.";
|
||||
}
|
||||
}
|
||||
mandatory true;
|
||||
description
|
||||
"The protocol layer where the error occurred.";
|
||||
}
|
||||
|
||||
leaf error-tag {
|
||||
type string;
|
||||
mandatory true;
|
||||
description
|
||||
"The enumerated error-tag.";
|
||||
}
|
||||
|
||||
leaf error-app-tag {
|
||||
type string;
|
||||
description
|
||||
"The application-specific error-tag.";
|
||||
}
|
||||
|
||||
leaf error-path {
|
||||
type instance-identifier;
|
||||
description
|
||||
"The YANG instance identifier associated
|
||||
with the error node.";
|
||||
}
|
||||
|
||||
leaf error-message {
|
||||
type string;
|
||||
description
|
||||
"A message describing the error.";
|
||||
}
|
||||
|
||||
anydata error-info {
|
||||
description
|
||||
"This anydata value MUST represent a container with
|
||||
zero or more data nodes representing additional
|
||||
error information.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grouping restconf {
|
||||
description
|
||||
"Conceptual grouping representing the RESTCONF
|
||||
root resource.";
|
||||
|
||||
container restconf {
|
||||
description
|
||||
"Conceptual container representing the RESTCONF
|
||||
root resource.";
|
||||
|
||||
container data {
|
||||
description
|
||||
"Container representing the datastore resource.
|
||||
Represents the conceptual root of all state data
|
||||
and configuration data supported by the server.
|
||||
The child nodes of this container can be any data
|
||||
resources that are defined as top-level data nodes
|
||||
from the YANG modules advertised by the server in
|
||||
the 'ietf-yang-library' module.";
|
||||
}
|
||||
|
||||
container operations {
|
||||
description
|
||||
"Container for all operation resources.
|
||||
|
||||
Each resource is represented as an empty leaf with the
|
||||
name of the RPC operation from the YANG 'rpc' statement.
|
||||
|
||||
For example, the 'system-restart' RPC operation defined
|
||||
in the 'ietf-system' module would be represented as
|
||||
an empty leaf in the 'ietf-system' namespace. This is
|
||||
a conceptual leaf and will not actually be found in
|
||||
the module:
|
||||
|
||||
module ietf-system {
|
||||
leaf system-reset {
|
||||
type empty;
|
||||
}
|
||||
}
|
||||
|
||||
To invoke the 'system-restart' RPC operation:
|
||||
|
||||
POST /restconf/operations/ietf-system:system-restart
|
||||
|
||||
To discover the RPC operations supported by the server:
|
||||
|
||||
GET /restconf/operations
|
||||
|
||||
In XML, the YANG module namespace identifies the module:
|
||||
|
||||
<system-restart
|
||||
xmlns='urn:ietf:params:xml:ns:yang:ietf-system'/>
|
||||
|
||||
In JSON, the YANG module name identifies the module:
|
||||
|
||||
{ 'ietf-system:system-restart' : [null] }
|
||||
";
|
||||
}
|
||||
leaf yang-library-version {
|
||||
type string {
|
||||
pattern '\d{4}-\d{2}-\d{2}';
|
||||
}
|
||||
config false;
|
||||
mandatory true;
|
||||
description
|
||||
"Identifies the revision date of the 'ietf-yang-library'
|
||||
module that is implemented by this RESTCONF server.
|
||||
Indicates the year, month, and day in YYYY-MM-DD
|
||||
numeric format.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue