diff --git a/CHANGELOG.md b/CHANGELOG.md
index f49bf034..463ab2c6 100644
--- a/CHANGELOG.md
+++ b/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
diff --git a/README.md b/README.md
index 47b72648..91c10429 100644
--- a/README.md
+++ b/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)
diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in
index 45677854..0a3c0ea3 100644
--- a/apps/backend/Makefile.in
+++ b/apps/backend/Makefile.in
@@ -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@
diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c
index 8280ebbe..e8437a88 100644
--- a/apps/backend/backend_client.c
+++ b/apps/backend/backend_client.c
@@ -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, "urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit") < 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, "", 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, "", 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)
diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c
index 5e9a5354..26fb145b 100644
--- a/apps/backend/backend_main.c
+++ b/apps/backend/backend_main.c
@@ -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;
diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c
index 711a8de0..a0dc92d3 100644
--- a/apps/backend/backend_plugin.c
+++ b/apps/backend/backend_plugin.c
@@ -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
diff --git a/apps/backend/backend_plugin.h b/apps/backend/backend_plugin.h
index 1071fc4e..270272c7 100644
--- a/apps/backend/backend_plugin.h
+++ b/apps/backend/backend_plugin.h
@@ -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,
diff --git a/apps/backend/backend_startup.c b/apps/backend/backend_startup.c
index 7de92e44..35204067 100644
--- a/apps/backend/backend_startup.c
+++ b/apps/backend/backend_startup.c
@@ -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 */
diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c
index 2bbce242..81ceb677 100644
--- a/apps/cli/cli_common.c
+++ b/apps/cli/cli_common.c
@@ -47,9 +47,6 @@
#include
#include
-#ifdef HAVE_CRYPT_H
-#include
-#endif
#include
#include
#include
diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c
index 3ca2c904..4651c5fc 100644
--- a/apps/cli/cli_main.c
+++ b/apps/cli/cli_main.c
@@ -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;
diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c
index 2b2f5791..31331f21 100644
--- a/apps/cli/cli_plugin.c
+++ b/apps/cli/cli_plugin.c
@@ -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;
diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c
index 6cf77547..635bbf8b 100644
--- a/apps/cli/cli_show.c
+++ b/apps/cli/cli_show.c
@@ -48,9 +48,6 @@
#include
#include
-#ifdef HAVE_CRYPT_H
-#include
-#endif
#include
#include
#include
diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c
index a2c90486..32e81496 100644
--- a/apps/netconf/netconf_main.c
+++ b/apps/netconf/netconf_main.c
@@ -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)
diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c
index e7613f87..05cf76e8 100644
--- a/apps/restconf/restconf_lib.c
+++ b/apps/restconf/restconf_lib.c
@@ -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");
}
diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c
index cc6dabef..e8cbe8a2 100644
--- a/apps/restconf/restconf_main.c
+++ b/apps/restconf/restconf_main.c
@@ -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("2016-06-21", NULL, &xt) < 0)
+ if (xml_parse_string("2016-06-21", 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, "%s", 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,15 +681,21 @@ 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);
+ 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 */
@@ -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;
diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c
index cc7f9f49..4208eca2 100644
--- a/apps/restconf/restconf_methods.c
+++ b/apps/restconf/restconf_methods.c
@@ -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");
diff --git a/configure b/configure
index d1172d87..2a1df8d0 100755
--- a/configure
+++ b/configure
@@ -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 :
diff --git a/configure.ac b/configure.ac
index ce48aff5..59c9658b 100644
--- a/configure.ac
+++ b/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 ]]
)
-# 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
diff --git a/doc/FAQ.md b/doc/FAQ.md
index de9f60d0..4c250cdf 100644
--- a/doc/FAQ.md
+++ b/doc/FAQ.md
@@ -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
diff --git a/example/main/Makefile.in b/example/main/Makefile.in
index 363d460b..36a27dc0 100644
--- a/example/main/Makefile.in
+++ b/example/main/Makefile.in
@@ -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
diff --git a/example/main/README.md b/example/main/README.md
index 2d00dec2..441e5aee 100644
--- a/example/main/README.md
+++ b/example/main/README.md
@@ -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.
diff --git a/example/main/clixon-example@2019-01-13.yang b/example/main/clixon-example@2019-07-23.yang
similarity index 85%
rename from example/main/clixon-example@2019-01-13.yang
rename to example/main/clixon-example@2019-07-23.yang
index da038f31..cfb4c629 100644
--- a/example/main/clixon-example@2019-01-13.yang
+++ b/example/main/clixon-example@2019-07-23.yang
@@ -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.";
diff --git a/example/main/example_backend.c b/example/main/example_backend.c
index 6c2040a9..5a4f3b29 100644
--- a/example/main/example_backend.c
+++ b/example/main/example_backend.c
@@ -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 */
diff --git a/include/clixon_config.h.in b/include/clixon_config.h.in
index 7bd27d76..a5b70be8 100644
--- a/include/clixon_config.h.in
+++ b/include/clixon_config.h.in
@@ -24,9 +24,6 @@
/* Define to 1 if you have the header file. */
#undef HAVE_CLIGEN_CLIGEN_H
-/* Define to 1 if you have the 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 header file. */
-#undef HAVE_LINUX_IF_VLAN_H
-
/* Define to 1 if you have the header file. */
#undef HAVE_MEMORY_H
diff --git a/lib/clixon/clixon_json.h b/lib/clixon/clixon_json.h
index d4b2b3d4..30488d0b 100644
--- a/lib/clixon/clixon_json.h
+++ b/lib/clixon/clixon_json.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
diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h
index ce8e20e1..2c91e0b9 100644
--- a/lib/clixon/clixon_plugin.h
+++ b/lib/clixon/clixon_plugin.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);
diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h
index c703f4f0..4070eac2 100644
--- a/lib/clixon/clixon_yang.h
+++ b/lib/clixon/clixon_yang.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);
diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c
index 6049df0b..609e7e07 100644
--- a/lib/src/clixon_json.c
+++ b/lib/src/clixon_json.c
@@ -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 no children */
+ NULL_CHILD=0, /* eg 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 1 */
ANY_CHILD, /* eg or */
};
/*! 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;
diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c
index 0f2fd622..c2d5cd3c 100644
--- a/lib/src/clixon_plugin.c
+++ b/lib/src/clixon_plugin.c
@@ -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){
diff --git a/lib/src/clixon_string.c b/lib/src/clixon_string.c
index 014ce52b..cfd07b0e 100644
--- a/lib/src/clixon_string.c
+++ b/lib/src/clixon_string.c
@@ -65,7 +65,7 @@
* if ((vec = clicon_strsep("/home/user/src/clixon", "/", &nvec)) == NULL)
* err;
* for (i=0; iys_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; iys_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;
}
+
diff --git a/lib/src/clixon_yang_internal.h b/lib/src/clixon_yang_internal.h
index fe7b2c49..a1410dda 100644
--- a/lib/src/clixon_yang_internal.h
+++ b/lib/src/clixon_yang_internal.h
@@ -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
diff --git a/lib/src/clixon_yang_parse.l b/lib/src/clixon_yang_parse.l
index 8eb87f70..1b7c9f46 100644
--- a/lib/src/clixon_yang_parse.l
+++ b/lib/src/clixon_yang_parse.l
@@ -104,7 +104,7 @@ identifier [A-Za-z_][A-Za-z0-9_\-\.]*
%%
/* Common tokens */
-[ \t]
+[ \t]
<> { return MY_EOF; }
\n { _YY->yy_linenum++; }
\r
@@ -199,7 +199,8 @@ identifier [A-Za-z_][A-Za-z0-9_\-\.]*
; { BEGIN(KEYWORD); return *yytext; }
\" { _YY->yy_lex_string_state =UNKNOWN; BEGIN(STRINGDQ); return *yytext; }
\{ { BEGIN(KEYWORD); return *yytext; }
-. { clixon_yang_parselval.string = strdup(yytext);
+[ \t]+ { return SEP; }
+[^{";: \t]+ { clixon_yang_parselval.string = strdup(yytext);
return CHARS; }
true { clixon_yang_parselval.string = strdup(yytext);
diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y
index 7e9d910a..84c48830 100644
--- a/lib/src/clixon_yang_parse.y
+++ b/lib/src/clixon_yang_parse.y
@@ -57,6 +57,7 @@
%token MY_EOF
%token SQ /* Single quote: ' */
+%token SEP /* Separators (at least one) */
%token CHARS
%token IDENTIFIER
%token 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 '}'
diff --git a/test/lib.sh b/test/lib.sh
index 5f4ed32d..5daa83b8 100755
--- a/test/lib.sh
+++ b/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<-23"
-
-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]}' "0123"
-
-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 < $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}' "-23"
+
+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]}' "0123"
+
+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": " x & x < y ]]>"}}'
new "json parse cdata xml"
expecteofx "$clixon_util_json -j -y $fyang" 0 "$JSON" "$JSON"
fi
+
rm -rf $dir
diff --git a/test/test_nacm.sh b/test/test_nacm.sh
index c50308ab..982ddcd4 100755
--- a/test/test_nacm.sh
+++ b/test/test_nacm.sh
@@ -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 "]]>]]>" "^]]>]]>$"
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
diff --git a/test/test_nacm_default.sh b/test/test_nacm_default.sh
index 5916ffa4..8da722c4 100755
--- a/test/test_nacm_default.sh
+++ b/test/test_nacm_default.sh
@@ -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"
diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh
index 102af693..d265d927 100755
--- a/test/test_nacm_ext.sh
+++ b/test/test_nacm_ext.sh
@@ -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;$"
diff --git a/test/test_nacm_module_read.sh b/test/test_nacm_module_read.sh
index 1d87a9c1..f95cac52 100755
--- a/test/test_nacm_module_read.sh
+++ b/test/test_nacm_module_read.sh
@@ -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 ']]>]]>' '^key42val42key43val43]]>]]>$'
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 ']]>]]>' '^key42val42key43val43]]>]]>$'
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 ']]>]]>' '^applicationaccess-deniederrordefault deny]]>]]>$'
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 '0]]>]]>' 0 '^042]]>]]>$'
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 '0]]>]]>' 0 '^042]]>]]>$'
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 '0]]>]]>' 0 '^applicationaccess-deniederroraccess denied]]>]]>$'
@@ -255,18 +255,18 @@ expecteof "$clixon_netconf -U guest -qf $cfg" 0 '42$RULES')" 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 '42$RULES')" 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="$RULES"
@@ -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 '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 -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:x -d '42')" 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 '42')" 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 '99')" 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 '99')" 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 '99')" 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 'key42str')" 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 'key42str')" 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 'key42str')" 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 'key42update')" 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 'key42update')" 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 'key42update')" 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
diff --git a/test/test_nacm_protocol.sh b/test/test_nacm_protocol.sh
index 59ce33c7..ee063a57 100755
--- a/test/test_nacm_protocol.sh
+++ b/test/test_nacm_protocol.sh
@@ -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 "]]>]]>" "^applicationaccess-deniederroraccess denied]]>]]>$"
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
diff --git a/test/test_perf_state.sh b/test/test_perf_state.sh
index 1449f7d4..17d3ef1b 100755
--- a/test/test_perf_state.sh
+++ b/test/test_perf_state.sh
@@ -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"
diff --git a/test/test_restconf.sh b/test/test_restconf.sh
index fe86cc65..fd4aecbd 100755
--- a/test/test_restconf.sh
+++ b/test/test_restconf.sh
@@ -34,7 +34,7 @@ cat < $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 "
"
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 '2016-06-21
+expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/restconf)" 0 '2016-06-21
'
# 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
diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh
index ec1486d7..ba39e427 100755
--- a/test/test_restconf2.sh
+++ b/test/test_restconf2.sh
@@ -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
diff --git a/test/test_restconf_err.sh b/test/test_restconf_err.sh
index fb5e27da..8c218c29 100755
--- a/test/test_restconf_err.sh
+++ b/test/test_restconf_err.sh
@@ -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
diff --git a/test/test_restconf_jukebox.sh b/test/test_restconf_jukebox.sh
new file mode 100755
index 00000000..9cabb0b2
--- /dev/null
+++ b/test/test_restconf_jukebox.sh
@@ -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
+
+# example
+cat < $cfg
+
+ $cfg
+ /usr/local/share/clixon
+ $IETFRFC
+ $fyang
+ false
+ /usr/local/var/$APPNAME/$APPNAME.sock
+ /usr/local/lib/$APPNAME/backend
+ $dir/restconf.pidfile
+ /usr/local/var/$APPNAME
+ true
+
+EOF
+
+cat < $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" "" "" ""
+
+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" '2016-06-21'
+
+# 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' 'urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit'
+
+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
diff --git a/test/test_restconf_listkey.sh b/test/test_restconf_listkey.sh
index cb0546e4..294f18f6 100755
--- a/test/test_restconf_listkey.sh
+++ b/test/test_restconf_listkey.sh
@@ -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 'xyxz0' http://localhost/restconf/data/list:c/a=xx,xy)" 0 'protocoloperation-failederrorapi-path keys do not match data keys
'
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"
diff --git a/test/test_rpc.sh b/test/test_rpc.sh
index 8190608a..0c956e03 100755
--- a/test/test_rpc.sh
+++ b/test/test_rpc.sh
@@ -70,20 +70,20 @@ new "netconf empty rpc"
expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^]]>]]>$'
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 '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+xml' -H 'Accept: application/yang-data+json' -d '0' 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 ']]>]]>' '^applicationmissing-elementsession-iderrorMandatory variable]]>]]>$'
diff --git a/test/test_stream.sh b/test/test_stream.sh
index 2f8af4bd..a4dcd703 100755
--- a/test/test_stream.sh
+++ b/test/test_stream.sh
@@ -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
diff --git a/test/test_submodule.sh b/test/test_submodule.sh
index fc8711d1..d64812b3 100755
--- a/test/test_submodule.sh
+++ b/test/test_submodule.sh
@@ -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
diff --git a/test/test_xml.sh b/test/test_xml.sh
index f24997ac..c3943ea5 100755
--- a/test/test_xml.sh
+++ b/test/test_xml.sh
@@ -12,7 +12,7 @@ new "xml parse"
expecteof "$clixon_util_xml" 0 "" "^$"
new "xml parse to json"
-expecteof "$clixon_util_xml -j" 0 "" '{"a": {"b": null}}'
+expecteof "$clixon_util_xml -j" 0 "" '{"a":{"b":null}}'
new "xml parse strange names"
expecteof "$clixon_util_xml" 0 "<_->" "<_->"
@@ -38,7 +38,7 @@ new "xml simple CDATA"
expecteofx "$clixon_util_xml" 0 '' ''
new "xml simple CDATA to json"
-expecteofx "$clixon_util_xml -j" 0 '' '{"a": "a text"}'
+expecteofx "$clixon_util_xml -j" 0 '' '{"a":"a text"}'
new "xml complex CDATA"
XML=$(cat <An example of escaped CE
]]>]]>$"
JSON=$(cat < 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 <single-quote character ' represented as ' and double-quote character as "
@@ -84,7 +84,7 @@ new "xml single and double quote"
expecteof "$clixon_util_xml" 0 "$XML" "single-quote character ' represented as ' and double-quote character as \""
JSON=$(cat <a\b" "a\b"
new "xml backspace to json"
-expecteofx "$clixon_util_xml -j" 0 "a\b" '{"a": "a\\b"}'
+expecteofx "$clixon_util_xml -j" 0 "a\b" '{"a":"a\\b"}'
new "Double quotes for attributes"
expecteof "$clixon_util_xml" 0 '' ''
diff --git a/test/test_yang_extension.sh b/test/test_yang_extension.sh
new file mode 100755
index 00000000..87328772
--- /dev/null
+++ b/test/test_yang_extension.sh
@@ -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 < $cfg
+
+ $cfg
+ /usr/local/share/clixon
+ $dir
+ $IETFRFC
+ $fyang
+ /usr/local/lib/$APPNAME/clispec
+ /usr/local/lib/$APPNAME/cli
+ /usr/local/lib/$APPNAME/netconf
+ /usr/local/lib/$APPNAME/backend
+ $APPNAME
+ /usr/local/var/$APPNAME/$APPNAME.sock
+ /usr/local/var/$APPNAME/$APPNAME.pidfile
+ 1
+ /usr/local/var/$APPNAME
+ true
+
+EOF
+
+cat < $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='a string'
+
+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 'a string]]>]]>' '^applicationunknown-elementfooerror'
+
+new "Add extension bar (is implemented)"
+expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 'a string]]>]]>' '^]]>]]>'
+
+new "netconf get config"
+expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "]]>]]>" '^a string]]>]]>$'
+
+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
diff --git a/test/test_yang_namespace.sh b/test/test_yang_namespace.sh
index 113d0426..07691ebe 100755
--- a/test/test_yang_namespace.sh
+++ b/test/test_yang_namespace.sh
@@ -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"
diff --git a/util/clixon_util_json.c b/util/clixon_util_json.c
index 2ae5bed3..9b8e338b 100644
--- a/util/clixon_util_json.c
+++ b/util/clixon_util_json.c
@@ -76,6 +76,7 @@ usage(char *argv0)
"\t-D \tDebug\n"
"\t-j \t\tOutput as JSON\n"
"\t-l \tLog on (s)yslog, std(e)rr, std(o)ut (stderr is default)\n"
+ "\t-p \t\tPretty-print output\n"
"\t-y \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;
diff --git a/yang/clixon/clixon-config@2019-06-05.yang b/yang/clixon/clixon-config@2019-06-05.yang
index 6a1112d8..86898ccf 100644
--- a/yang/clixon/clixon-config@2019-06-05.yang
+++ b/yang/clixon/clixon-config@2019-06-05.yang
@@ -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;
diff --git a/yang/standard/Makefile.in b/yang/standard/Makefile.in
index 47061637..44909ddb 100644
--- a/yang/standard/Makefile.in
+++ b/yang/standard/Makefile.in
@@ -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
diff --git a/yang/standard/ietf-restconf@2017-01-26.yang b/yang/standard/ietf-restconf@2017-01-26.yang
new file mode 100644
index 00000000..b47455b8
--- /dev/null
+++ b/yang/standard/ietf-restconf@2017-01-26.yang
@@ -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:
+ WG List:
+
+ Author: Andy Bierman
+
+
+ Author: Martin Bjorklund
+
+
+ Author: Kent Watsen
+ ";
+
+ 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:
+
+
+
+ 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.";
+ }
+ }
+ }
+
+}