diff --git a/CHANGELOG.md b/CHANGELOG.md
index efc0cf5b..fdb7ae89 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@
* Restconf RFC 8040 increased feature compliance
* RESTCONF PATCH (plain patch) is being implemented according to RFC 8040 Section 4.6.1
* Note RESTCONF plain patch is different from RFC 8072 "YANG Patch Media Type" which is not implemented
+ * RESTCONF "content" query parameter supported
* RESTCONF "insert" and "point" query parameters supported
* Applies to ordered-by-user leaf and leaf-lists
* RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns:
@@ -25,6 +26,8 @@
* The main example explains how to implement a Yang extension in a backend plugin.
### API changes on existing features (you may need to change your code)
+* New clixon-lib@2019-08-13.yang revision
+ * Added new rpc: `get-state` to get only state info in the internal Restconf/backend communication
* Netconf edit-config "operation" attribute namespace check is enforced
* This is enforced: `
* This was previously allowed: `
@@ -51,6 +54,8 @@
* pseudo-plugin added, to enable callbacks also for main programs. Useful for extensions
### Corrected Bugs
+* If `ietf-netconf.yang` was imported from any yang module, client/backend communication stops working.
+ * Fixed by adding supported netconf features before loading other yang modules
* RESTCONF JSON identity had wrong namespace in sub-objetcs
* Showed if you GET an object with JSON encoding that have identities
* Fixed Segv in nacm write when MERGE and creating object
diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c
index 07329adc..1d532db1 100644
--- a/apps/backend/backend_client.c
+++ b/apps/backend/backend_client.c
@@ -942,6 +942,94 @@ from_client_get(clicon_handle h,
return retval;
}
+/*! Retrieve device state information only
+ *
+ * This is a CLIXON specific RPC
+ * @param[in] h Clicon handle
+ * @param[in] xe Request:
+ * @param[out] cbret Return xml tree, eg ...,
+ * The set of namespace declarations are those in scope on the
+ * element.
+ */
+ else
+ if (xml_nsctx_node(xfilter, &nsc) < 0)
+ goto done;
+ }
+ /* Get state data from plugins as defined by plugin_statedata(), if any */
+ clicon_err_reset();
+ if ((ret = client_statedata(h, xpath, nsc, &xret)) < 0)
+ goto done;
+ if (ret == 0){ /* Error from callback (error in xret) */
+ if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0)
+ goto done;
+ goto ok;
+ }
+ /* Pre-NACM access step */
+ if ((ret = nacm_access_pre(h, username, NACM_DATA, &xnacm)) < 0)
+ goto done;
+ if (ret == 0){ /* Do NACM validation */
+ if (xpath_vec_nsc(xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
+ goto done;
+ /* NACM datanode/module read validation */
+ if (nacm_datanode_read(xret, xvec, xlen, username, xnacm) < 0)
+ goto done;
+ }
+ cprintf(cbret, ""); /* OK */
+ if (xret==NULL)
+ cprintf(cbret, "");
+ else{
+ if (xml_name_set(xret, "data") < 0)
+ goto done;
+ if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0)
+ goto done;
+ }
+ cprintf(cbret, "");
+ ok:
+ retval = 0;
+ done:
+ clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
+ if (xnacm)
+ xml_free(xnacm);
+ if (xvec)
+ free(xvec);
+ if (nsc)
+ xml_nsctx_free(nsc);
+ if (xret)
+ xml_free(xret);
+ return retval;
+}
+
+
/*! Request graceful termination of a NETCONF session.
* @param[in] h Clicon handle
* @param[in] xe Request:
@@ -1283,7 +1371,7 @@ from_client_msg(clicon_handle h,
goto reply; /* Dont quit here on user callbacks */
}
if (ret == 0){ /* not handled by callback */
- if (netconf_operation_failed(cbret, "application", "Callback not recognized")< 0)
+ if (netconf_operation_not_supported(cbret, "application", "RPC operation not supported")< 0)
goto done;
goto reply;
}
@@ -1424,6 +1512,9 @@ backend_rpc_init(clicon_handle h)
"urn:ietf:params:xml:ns:netmod:notification", "create-subscription") < 0)
goto done;
/* Clixon RPC */
+ if (rpc_callback_register(h, from_client_get_state, NULL,
+ "http://clicon.org/lib", "get-state") < 0)
+ goto done;
if (rpc_callback_register(h, from_client_debug, NULL,
"http://clicon.org/lib", "debug") < 0)
goto done;
diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c
index 26fb145b..5a18f89f 100644
--- a/apps/backend/backend_main.c
+++ b/apps/backend/backend_main.c
@@ -570,6 +570,12 @@ main(int argc,
if (xmldb_connect(h) < 0)
goto done;
+ /* Add (hardcoded) netconf features in case ietf-netconf loaded here
+ * Otherwise it is loaded in netconf_module_load below
+ */
+ if (netconf_module_features(h) < 0)
+ goto done;
+
/* Create top-level yang spec and store as option */
if ((yspec = yspec_new()) == NULL)
goto done;
@@ -601,7 +607,8 @@ main(int argc,
/* Load yang module library, RFC7895 */
if (yang_modules_init(h) < 0)
goto done;
- /* Add netconf yang spec, used by netconf client and as internal protocol */
+ /* Add netconf yang spec, used by netconf client and as internal protocol
+ */
if (netconf_module_load(h) < 0)
goto done;
/* Load yang restconf module */
diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c
index 4651c5fc..d44973e4 100644
--- a/apps/cli/cli_main.c
+++ b/apps/cli/cli_main.c
@@ -472,6 +472,12 @@ main(int argc, char **argv)
clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
goto done;
+ /* Add (hardcoded) netconf features in case ietf-netconf loaded here
+ * Otherwise it is loaded in netconf_module_load below
+ */
+ if (netconf_module_features(h) < 0)
+ goto done;
+
/* Create top-level and store as option */
if ((yspec = yspec_new()) == NULL)
goto done;
diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c
index 32e81496..f1fadb92 100644
--- a/apps/netconf/netconf_main.c
+++ b/apps/netconf/netconf_main.c
@@ -484,6 +484,12 @@ main(int argc,
/* Access the remaining argv/argc options (after --) w clicon-argv_get() */
clicon_argv_set(h, argv0, argc, argv);
+ /* Add (hardcoded) netconf features in case ietf-netconf loaded here
+ * Otherwise it is loaded in netconf_module_load below
+ */
+ if (netconf_module_features(h) < 0)
+ goto done;
+
/* Create top-level yang spec and store as option */
if ((yspec = yspec_new()) == NULL)
goto done;
diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c
index ddde425e..b4a245db 100644
--- a/apps/restconf/restconf_lib.c
+++ b/apps/restconf/restconf_lib.c
@@ -143,6 +143,7 @@ static const map_str2int http_reason_phrase_map[] = {
};
/* See RFC 8040
+ * @see restconf_media_str2int
*/
static const map_str2int http_media_map[] = {
{"application/yang-data+xml", YANG_DATA_XML},
@@ -152,6 +153,16 @@ static const map_str2int http_media_map[] = {
{NULL, -1}
};
+/* See RFC 8040 4.8.1
+ * @see query_content_str2int
+ */
+static const map_str2int query_content_map[] = {
+ {"config", CONTENT_CONFIG},
+ {"nonconfig", CONTENT_NONCONFIG},
+ {"all", CONTENT_ALL},
+ {NULL, -1}
+};
+
int
restconf_err2code(char *tag)
{
@@ -176,6 +187,18 @@ restconf_media_int2str(restconf_media media)
return clicon_int2str(http_media_map, media);
}
+const query_content
+query_content_str2int(char *str)
+{
+ return clicon_str2int(query_content_map, str);
+}
+
+const char *
+query_content_int2str(query_content nr)
+{
+ return clicon_int2str(query_content_map, nr);
+}
+
/*! Return media_in from Content-Type, -1 if not found or unrecognized
* @note media-type syntax does not support parameters
* @see RFC7231 Sec 3.1.1.1 for media-type syntax type:
diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h
index 6c4a2613..a7da11d5 100644
--- a/apps/restconf/restconf_lib.h
+++ b/apps/restconf/restconf_lib.h
@@ -57,6 +57,16 @@ enum restconf_media{
};
typedef enum restconf_media restconf_media;
+/*! Content query parameter RFC 8040 Sec 4.8.1
+ */
+enum query_content{
+ CONTENT_CONFIG,
+ CONTENT_NONCONFIG,
+ CONTENT_ALL /* default */
+
+};
+typedef enum query_content query_content;
+
/*
* Prototypes (also in clixon_restconf.h)
*/
@@ -66,6 +76,8 @@ const char *restconf_code2reason(int code);
const restconf_media restconf_media_str2int(char *media);
const char *restconf_media_int2str(restconf_media media);
restconf_media restconf_content_type(FCGX_Request *r);
+const query_content query_content_str2int(char *str);
+const char *query_content_int2str(query_content nr);
int restconf_badrequest(FCGX_Request *r);
int restconf_unauthorized(FCGX_Request *r);
int restconf_forbidden(FCGX_Request *r);
diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c
index 31177590..eb93c6eb 100644
--- a/apps/restconf/restconf_main.c
+++ b/apps/restconf/restconf_main.c
@@ -695,6 +695,12 @@ main(int argc,
/* Access the remaining argv/argc options (after --) w clicon-argv_get() */
clicon_argv_set(h, argv0, argc, argv);
+ /* Add (hardcoded) netconf features in case ietf-netconf loaded here
+ * Otherwise it is loaded in netconf_module_load below
+ */
+ if (netconf_module_features(h) < 0)
+ goto done;
+
/* Create top-level yang spec and store as option */
if ((yspec = yspec_new()) == NULL)
goto done;
diff --git a/apps/restconf/restconf_methods_get.c b/apps/restconf/restconf_methods_get.c
index 1f7e8554..dc9f5918 100644
--- a/apps/restconf/restconf_methods_get.c
+++ b/apps/restconf/restconf_methods_get.c
@@ -114,12 +114,31 @@ api_data_get2(clicon_handle h,
int ret;
char *namespace = NULL;
cvec *nsc = NULL;
+ char *str;
+ query_content content = CONTENT_ALL;
clicon_debug(1, "%s", __FUNCTION__);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
+ /* Check for content attribute */
+ if ((str = cvec_find_str(qvec, "content")) != NULL){
+ clicon_debug(1, "%s content=%s", __FUNCTION__, str);
+ if ((content = query_content_str2int(str)) == -1){
+ if (netconf_bad_attribute_xml(&xerr, "application",
+ "content", "Unrecognized value of content attribute") < 0)
+ goto done;
+ if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
+ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
+ goto done;
+ }
+ if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
+ goto done;
+ goto ok;
+ }
+ }
+
if ((cbpath = cbuf_new()) == NULL)
goto done;
cprintf(cbpath, "/");
@@ -144,7 +163,21 @@ api_data_get2(clicon_handle h,
* xpath expressions */
if ((nsc = xml_nsctx_init(NULL, namespace)) == NULL)
goto done;
- if (clicon_rpc_get(h, xpath, namespace, &xret) < 0){
+ switch (content){
+ case CONTENT_CONFIG:
+ ret = clicon_rpc_get_config(h, "running", xpath, namespace, &xret);
+ break;
+ case CONTENT_NONCONFIG:
+ ret = clicon_rpc_get_state(h, xpath, namespace, &xret);
+ break;
+ case CONTENT_ALL:
+ ret = clicon_rpc_get(h, xpath, namespace, &xret);
+ break;
+ default:
+ clicon_err(OE_XML, EINVAL, "Invalid content attribute %d", content);
+ break;
+ }
+ if (ret < 0){
if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0)
goto done;
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
diff --git a/example/main/example_backend.c b/example/main/example_backend.c
index 647e892b..234c8d92 100644
--- a/example/main/example_backend.c
+++ b/example/main/example_backend.c
@@ -31,6 +31,12 @@
***** END LICENSE BLOCK *****
+ * The example have the following optional arguments that you can pass as
+ * argc/argv after -- in clixon_backend:
+ * -r enable the reset function
+ * -s enable the state function
+ * -u enable upgrade function - auto-upgrade testing
+ * -t enable transaction logging (cal syslog for every transaction)
*/
#include
#include
@@ -284,7 +290,7 @@ example_copy_extra(clicon_handle h, /* Clicon handle */
type string;
}
}
- * This yang snippet is present in clixon-example.yang for exampl.
+ * This yang snippet is present in clixon-example.yang for example.
*/
int
example_statedata(clicon_handle h,
@@ -300,9 +306,12 @@ example_statedata(clicon_handle h,
cxobj *xt = NULL;
char *name;
cvec *nsc1 = NULL;
+ cvec *nsc2 = NULL;
+ yang_stmt *yspec = NULL;
if (!_state)
goto ok;
+ yspec = clicon_dbspec_yang(h);
/* Example of statedata, in this case merging state data with
* state information. In this case adding dummy interface operation state
@@ -311,37 +320,75 @@ example_statedata(clicon_handle h,
if (xmldb_get0(h, "running", nsc, xpath, 1, &xt, NULL) < 0)
goto done;
- /* Here a separate namespace context nsc1 is created. The original nsc
- * created by the system cannot be used trivially, since we dont know
- * the prefixes, although we could by a complex mechanism find the prefix
- * (if it exists) and use that when creating our xpath.
- * But it is easier creating a new namespace context nsc1.
- */
- if ((nsc1 = xml_nsctx_init(NULL, "urn:ietf:params:xml:ns:yang:ietf-interfaces")) == NULL)
- goto done;
- if (xpath_vec_nsc(xt, nsc1, "/interfaces/interface/name", &xvec, &xlen) < 0)
- goto done;
- if (xlen){
- cprintf(cb, "");
- for (i=0; i%sup", name);
- }
- cprintf(cb, "");
- if (xml_parse_string(cbuf_get(cb), NULL, &xstate) < 0)
- goto done;
- }
- if (xml_parse_string(""
+ if (yang_find_module_by_namespace(yspec, "urn:ietf:params:xml:ns:yang:ietf-interfaces") != NULL){
+ /* Here a separate namespace context nsc1 is created. The original nsc
+ * created by the system cannot be used trivially, since we dont know
+ * the prefixes, although we could by a complex mechanism find the prefix
+ * (if it exists) and use that when creating our xpath.
+ * But it is easier creating a new namespace context nsc1.
+ */
+ if ((nsc1 = xml_nsctx_init(NULL, "urn:ietf:params:xml:ns:yang:ietf-interfaces")) == NULL)
+ goto done;
+ if (xpath_vec_nsc(xt, nsc1, "/interfaces/interface/name", &xvec, &xlen) < 0)
+ goto done;
+ if (xlen){
+ cprintf(cb, "");
+ for (i=0; i%sup", name);
+ }
+ cprintf(cb, "");
+ if (xml_parse_string(cbuf_get(cb), NULL, &xstate) < 0)
+ goto done;
+ }
+ }
+ /* State in test_yang.sh , test_restconf.sh and test_order.sh */
+ if (yang_find_module_by_namespace(yspec, "urn:example:clixon") != NULL){
+ if (xml_parse_string(""
"42"
"41"
"43" /* should not be ordered */
"", NULL, &xstate) < 0)
- goto done;
+ goto done; /* For the case when urn:example:clixon is not loaded */
+ }
+ /* Event state from RFC8040 Appendix B.3.1
+ * Note: (1) order is by-system so is different,
+ * (2) event-count is XOR on name, so is not 42 and 4
+ */
+ if (yang_find_module_by_namespace(yspec, "urn:example:events") != NULL){
+ if ((nsc2 = xml_nsctx_init(NULL, "urn:example:events")) == NULL)
+ goto done;
+ if (xvec){
+ free(xvec);
+ xvec = NULL;
+ }
+ if (xpath_vec_nsc(xt, nsc2, "/events/event/name", &xvec, &xlen) < 0)
+ goto done;
+ if (xlen){
+ int j = 0;
+ int c;
+ cprintf(cb, "");
+
+ for (i=0; i%s%d", name, c);
+ }
+ cprintf(cb, "");
+ if (xml_parse_string(cbuf_get(cb), NULL, &xstate) < 0)
+ goto done;
+ }
+ }
+
ok:
retval = 0;
done:
if (nsc1)
xml_nsctx_free(nsc1);
+ if (nsc2)
+ xml_nsctx_free(nsc2);
if (xt)
xml_free(xt);
if (cb)
@@ -389,7 +436,6 @@ example_extension(clicon_handle h,
return retval;
}
-
/*! Testcase upgrade function moving interfaces-state to interfaces
* @param[in] h Clicon handle
* @param[in] xn XML tree to be updated
diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h
index 83fc2117..ccd9b528 100644
--- a/lib/clixon/clixon_netconf_lib.h
+++ b/lib/clixon/clixon_netconf_lib.h
@@ -47,6 +47,7 @@ int netconf_invalid_value_xml(cxobj **xret, char *type, char *message);
int netconf_too_big(cbuf *cb, char *type, char *message);
int netconf_missing_attribute(cbuf *cb, char *type, char *info, char *message);
int netconf_bad_attribute(cbuf *cb, char *type, char *info, char *message);
+int netconf_bad_attribute_xml(cxobj **xret, char *type, char *info, char *message);
int netconf_unknown_attribute(cbuf *cb, char *type, char *info, char *message);
int netconf_missing_element(cbuf *cb, char *type, char *element, char *message);
int netconf_missing_element_xml(cxobj **xret, char *type, char *element, char *message);
@@ -72,6 +73,7 @@ int netconf_malformed_message_xml(cxobj **xret, char *message);
int netconf_data_not_unique_xml(cxobj **xret, cxobj *x, cvec *cvk);
int netconf_minmax_elements_xml(cxobj **xret, cxobj *x, int max);
int netconf_trymerge(cxobj *x, yang_stmt *yspec, cxobj **xret);
+int netconf_module_features(clicon_handle h);
int netconf_module_load(clicon_handle h);
char *netconf_db_find(cxobj *xn, char *name);
int netconf_err2cb(cxobj *xerr, cbuf **cberr);
diff --git a/lib/clixon/clixon_proto_client.h b/lib/clixon/clixon_proto_client.h
index 0ac3783b..d49d1314 100644
--- a/lib/clixon/clixon_proto_client.h
+++ b/lib/clixon/clixon_proto_client.h
@@ -53,6 +53,7 @@ int clicon_rpc_delete_config(clicon_handle h, char *db);
int clicon_rpc_lock(clicon_handle h, char *db);
int clicon_rpc_unlock(clicon_handle h, char *db);
int clicon_rpc_get(clicon_handle h, char *xpath, char *namespace, cxobj **xret);
+int clicon_rpc_get_state(clicon_handle h, char *xpath, char *namespace, cxobj **xret);
int clicon_rpc_close_session(clicon_handle h);
int clicon_rpc_kill_session(clicon_handle h, int session_id);
int clicon_rpc_validate(clicon_handle h, char *db);
diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c
index bb28debf..c237cc00 100644
--- a/lib/src/clixon_netconf_lib.c
+++ b/lib/src/clixon_netconf_lib.c
@@ -271,34 +271,67 @@ netconf_bad_attribute(cbuf *cb,
char *info,
char *message)
{
- int retval = -1;
- char *encstr = NULL;
+ int retval = -1;
+ cxobj *xret = NULL;
- if (cprintf(cb, ""
- "%s"
- "bad-attribute"
- "%s"
- "error",
- type, info) <0)
- goto err;
+ if (netconf_bad_attribute_xml(&xret, type, info, message) < 0)
+ goto done;
+ if (clicon_xml2cbuf(cb, xret, 0, 0) < 0)
+ goto done;
+ retval = 0;
+ done:
+ if (xret)
+ xml_free(xret);
+ return retval;
+}
+
+/*! Create Netconf bad-attribute error XML tree according to RFC 6241 App A
+ *
+ * An attribute value is not correct; e.g., wrong type,
+ * out of range, pattern mismatch.
+ * @param[out] xret Error XML tree. Free with xml_free after use
+ * @param[in] type Error type: "rpc", "application" or "protocol"
+ * @param[in] info bad-attribute or bad-element xml
+ * @param[in] message Error message (will be XML encoded)
+ */
+int
+netconf_bad_attribute_xml(cxobj **xret,
+ char *type,
+ char *info,
+ char *message)
+{
+ int retval = -1;
+ cxobj *xerr = NULL;
+ char *encstr = NULL;
+
+ if (*xret == NULL){
+ if ((*xret = xml_new("rpc-reply", NULL, NULL)) == NULL)
+ goto done;
+ }
+ else if (xml_name_set(*xret, "rpc-reply") < 0)
+ goto done;
+ if ((xerr = xml_new("rpc-error", *xret, NULL)) == NULL)
+ goto done;
+ if (xml_parse_va(&xerr, NULL, "%s"
+ "bad-attribute"
+ "%s"
+ "error", type, info) < 0)
+ goto done;
if (message){
if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
- if (cprintf(cb, "%s", encstr) < 0)
- goto err;
+ if (xml_parse_va(&xerr, NULL, "%s",
+ encstr) < 0)
+ goto done;
}
- if (cprintf(cb, "") <0)
- goto err;
retval = 0;
done:
if (encstr)
free(encstr);
return retval;
- err:
- clicon_err(OE_XML, errno, "cprintf");
- goto done;
}
+
/*! Create Netconf unknwon-attribute error XML tree according to RFC 6241 App A
*
* An unexpected attribute is present.
@@ -462,6 +495,7 @@ netconf_bad_element(cbuf *cb,
xml_free(xret);
return retval;
}
+
int
netconf_bad_element_xml(cxobj **xret,
char *type,
@@ -1208,19 +1242,26 @@ netconf_trymerge(cxobj *x,
}
/*! Load ietf netconf yang module and set enabled features
- * The features added are (in order):
+ *
+ * This function should be called after options loaded but before yang modules.
+ * (a yang module may import ietf-netconf and then features must be set)
+ * @param[in] h Clixon handle
+ * @retval 0 OK
+ * @retval -1 Error
+ * The features added are (in order) (numbers are section# in RFC6241):
* candidate (8.3)
* validate (8.6)
* startup (8.7)
* xpath (8.9)
+ * @see netconf_module_load that is called later
*/
int
-netconf_module_load(clicon_handle h)
+netconf_module_features(clicon_handle h)
{
int retval = -1;
cxobj *xc;
yang_stmt *yspec;
-
+
yspec = clicon_dbspec_yang(h);
if ((xc = clicon_conf_xml(h)) == NULL){
clicon_err(OE_CFG, ENOENT, "Clicon configuration not loaded");
@@ -1233,6 +1274,24 @@ netconf_module_load(clicon_handle h)
goto done;
if (xml_parse_string("ietf-netconf:xpath", yspec, &xc) < 0)
goto done;
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Load ietf netconf yang module and set enabled features
+ * @param[in] h Clixon handle
+ * @retval 0 OK
+ * @retval -1 Error
+ * @see netconf_module_feature should be called before any yang modules
+ */
+int
+netconf_module_load(clicon_handle h)
+{
+ int retval = -1;
+ yang_stmt *yspec;
+
+ yspec = clicon_dbspec_yang(h);
/* Load yang spec */
if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec)< 0)
goto done;
diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c
index 98ed0fdd..2cedc722 100644
--- a/lib/src/clixon_proto_client.c
+++ b/lib/src/clixon_proto_client.c
@@ -615,6 +615,85 @@ clicon_rpc_get(clicon_handle h,
return retval;
}
+/*! Get database state data, clixon extension
+ * @param[in] h Clicon handle
+ * @param[in] xpath XPath in a filter stmt (or NULL/"" for no filter)
+ * @param[in] namespace Namespace associated w xpath
+ * @param[out] xt XML tree. Free with xml_free.
+ * Either or .
+ * @retval 0 OK
+ * @retval -1 Error, fatal or xml
+ * @note if xpath is set but namespace is NULL, the default, netconf base
+ * namespace will be used which is most probably wrong.
+ * @code
+ * cxobj *xt = NULL;
+ * if (clicon_rpc_get_state(h, "/hello/world", "urn:example:hello", &xt) < 0)
+ * err;
+ * if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){
+ * clicon_rpc_generate_error(xerr);
+ * err;
+ * }
+ * if (xt)
+ * xml_free(xt);
+ * @endcode
+ * @see clicon_rpc_generate_error
+ */
+int
+clicon_rpc_get_state(clicon_handle h,
+ char *xpath,
+ char *namespace,
+ cxobj **xt)
+{
+ int retval = -1;
+ struct clicon_msg *msg = NULL;
+ cbuf *cb = NULL;
+ cxobj *xret = NULL;
+ cxobj *xd;
+ char *username;
+
+ if ((cb = cbuf_new()) == NULL)
+ goto done;
+ cprintf(cb, "");
+ if (xpath && strlen(xpath)) {
+ if (namespace)
+ cprintf(cb, "",
+ xpath, namespace);
+ else /* If xpath != /, this will probably yield an error later */
+ cprintf(cb, "", xpath);
+ }
+ cprintf(cb, "");
+ if ((msg = clicon_msg_encode("%s", cbuf_get(cb))) == NULL)
+ goto done;
+ if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
+ goto done;
+ /* Send xml error back: first check error, then ok */
+ if ((xd = xpath_first(xret, "/rpc-reply/rpc-error")) != NULL)
+ xd = xml_parent(xd); /* point to rpc-reply */
+ else if ((xd = xpath_first(xret, "/rpc-reply/data")) == NULL)
+ if ((xd = xml_new("data", NULL, NULL)) == NULL)
+ goto done;
+ if (xt){
+ if (xml_rm(xd) < 0)
+ goto done;
+ *xt = xd;
+ }
+ retval = 0;
+ done:
+ if (cb)
+ cbuf_free(cb);
+ if (xret)
+ xml_free(xret);
+ if (msg)
+ free(msg);
+ return retval;
+}
+
+
/*! Close a (user) session
* @param[in] h CLICON handle
* @retval 0 OK
diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c
index 83a9403a..6344245e 100644
--- a/lib/src/clixon_xml_map.c
+++ b/lib/src/clixon_xml_map.c
@@ -3196,7 +3196,7 @@ xml_merge(cxobj *x0,
goto done;
if (ymod == NULL){
if (reason &&
- (*reason = strdup("No namespace in XML tree found")) == NULL){
+ (*reason = strdup("Namespace not found or yang spec not loaded")) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
diff --git a/test/test_restconf.sh b/test/test_restconf.sh
index 38ca60a6..6fa21d4c 100755
--- a/test/test_restconf.sh
+++ b/test/test_restconf.sh
@@ -74,12 +74,12 @@ expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/r
# Should be alphabetically ordered
new "restconf get restconf/operations. RFC8040 3.3.2 (json)"
-expecteq "$(curl -sG http://localhost/restconf/operations)" 0 '{"operations": {"clixon-example:client-rpc": null,"clixon-example:empty": null,"clixon-example:optional": null,"clixon-example:example": null,"clixon-lib:debug": null,"clixon-lib:ping": null,"ietf-netconf:get-config": null,"ietf-netconf:edit-config": null,"ietf-netconf:copy-config": null,"ietf-netconf:delete-config": null,"ietf-netconf:lock": null,"ietf-netconf:unlock": null,"ietf-netconf:get": null,"ietf-netconf:close-session": null,"ietf-netconf:kill-session": null,"ietf-netconf:commit": null,"ietf-netconf:discard-changes": null,"ietf-netconf:validate": null,"clixon-rfc5277:create-subscription": null}}
+expecteq "$(curl -sG http://localhost/restconf/operations)" 0 '{"operations": {"clixon-example:client-rpc": null,"clixon-example:empty": null,"clixon-example:optional": null,"clixon-example:example": null,"clixon-lib:debug": null,"clixon-lib:ping": null,"clixon-lib:get-state": null,"ietf-netconf:get-config": null,"ietf-netconf:edit-config": null,"ietf-netconf:copy-config": null,"ietf-netconf:delete-config": null,"ietf-netconf:lock": null,"ietf-netconf:unlock": null,"ietf-netconf:get": null,"ietf-netconf:close-session": null,"ietf-netconf:kill-session": null,"ietf-netconf:commit": null,"ietf-netconf:discard-changes": null,"ietf-netconf:validate": null,"clixon-rfc5277:create-subscription": null}}
'
new "restconf get restconf/operations. RFC8040 3.3.2 (xml)"
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations)
-expect=''
+expect=''
match=`echo $ret | grep -EZo "$expect"`
if [ -z "$match" ]; then
err "$expect" "$ret"
diff --git a/test/test_restconf_jukebox.sh b/test/test_restconf_jukebox.sh
index 411adf9c..3d5f2edf 100755
--- a/test/test_restconf_jukebox.sh
+++ b/test/test_restconf_jukebox.sh
@@ -8,6 +8,7 @@ APPNAME=example
cfg=$dir/conf.xml
fjukebox=$dir/example-jukebox.yang
+fcontent=$dir/example-events.yang
# A "system" module as defined in B.2.4
cat < $dir/example-system.yang
@@ -38,6 +39,25 @@ cat < $cfg
EOF
+# yang B.3.1. "content" Parameter
+cat < $fcontent
+ module example-events {
+ namespace "urn:example:events";
+ prefix "ex";
+ container events {
+ list event {
+ key name;
+ leaf name { type string; }
+ leaf description { type string; }
+ leaf event-count {
+ type uint32;
+ config false;
+ }
+ }
+ }
+}
+EOF
+
# Common Jukebox spec (fjukebox must be set)
. ./jukebox.sh
@@ -50,8 +70,8 @@ if [ $BE -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
+ new "start backend -s init -f $cfg -- -s"
+ start_backend -s init -f "$cfg" -- -s
fi
new "kill old restconf daemon"
@@ -77,7 +97,7 @@ expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' http://loca
# This just catches the header and the jukebox module, the RFC has foo and bar which
# seems wrong to recreate
new "B.1.2. Retrieve the Server Module Information"
-expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/ietf-yang-library:modules-state)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" '{"ietf-yang-library:modules-state":{"module-set-id":' '"module":\[{"name":"example-jukebox","revision":"2016-08-15","namespace":"http://example.com/ns/example-jukebox","conformance-type":"implement"}'
+expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/ietf-yang-library:modules-state)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" '{"ietf-yang-library:modules-state":{"module-set-id":' '"module":\[{"name":"example-events","revision":\[null\],"namespace":"urn:example:events","conformance-type":"implement"},{"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 -si -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/capabilities)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+xml" 'Cache-Control: no-cache' 'urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit'
@@ -129,6 +149,22 @@ expectpart "$(curl -si -X PATCH http://localhost/restconf/data/example-jukebox:j
new "B.2.5. Check edit"
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Nick%20Cave%20and%20the%20Bad%20Seeds -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'Nick Cave and the Bad SeedsTender Prey1988The Good Son1990'
+# note reverse order of down/up as it is ordered by system and down is before up
+new 'B.3.1. "content" Parameter (preamble, add content)'
+expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-events:events -d '{"example-events:events":{"event":[{"name":"interface-down","description":"Interface down notification count"},{"name":"interface-up","description":"Interface up notification count"}]}}')" 0 "HTTP/1.1 201 Created"
+
+new 'B.3.1. "content" Parameter (wrong content)'
+expectpart "$(curl -si -X GET http://localhost/restconf/data/example-events:events?content=kalle -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-attribute","error-info":{"bad-attribute":"content"},"error-severity":"error","error-message":"Unrecognized value of content attribute"}}}'
+
+new 'B.3.1. "content" Parameter example 1: content=all'
+expectpart "$(curl -si -X GET http://localhost/restconf/data/example-events:events?content=all -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-events:events":{"event":\[{"name":"interface-down","description":"Interface down notification count","event-count":90},{"name":"interface-up","description":"Interface up notification count","event-count":77}\]}}'
+
+new 'B.3.1. "content" Parameter example 2: content=config'
+expectpart "$(curl -si -X GET http://localhost/restconf/data/example-events:events?content=config -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-events:events":{"event":\[{"name":"interface-down","description":"Interface down notification count"},{"name":"interface-up","description":"Interface up notification count"}\]}}'
+
+new 'B.3.1. "content" Parameter example 3: content=nonconfig'
+expectpart "$(curl -si -X GET http://localhost/restconf/data/example-events:events?content=nonconfig -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-events:events":{"event":\[{"name":"interface-down","event-count":90},{"name":"interface-up","event-count":77}\]}}'
+
new "restconf DELETE whole datastore"
expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 ""
@@ -188,7 +224,6 @@ expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:ext
if false; then # NYI
new "B.2.2. Detect Datastore Resource Entity-Tag Change" # XXX done except entity-changed
-new 'B.3.1. "content" Parameter'
new 'B.3.2. "depth" Parameter'
new 'B.3.3. "fields" Parameter'
new 'B.3.6. "filter" Parameter'
diff --git a/test/test_yang.sh b/test/test_yang.sh
index b962201a..3c1891ee 100755
--- a/test/test_yang.sh
+++ b/test/test_yang.sh
@@ -2,7 +2,7 @@
# Yang test: multi-keys and empty type
# Magic line must be first in script (see README.md)
-s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
+s="$_" ; . ./lib.sh || if [ "$s" = "$0" ]; then exit 0; else return 0; fi
APPNAME=example
@@ -143,14 +143,14 @@ EOF
new "test params: -f $cfg"
-if [ $BE -ne 0 ]; then
+if [ "$BE" -ne 0 ]; then
new "kill old backend"
- sudo clixon_backend -zf $cfg
+ sudo clixon_backend -zf "$cfg"
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg"
- start_backend -s init -f $cfg
+ start_backend -s init -f "$cfg"
new "waiting"
wait_backend
@@ -280,18 +280,18 @@ expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$"
-if [ $BE -eq 0 ]; then
+if [ "$BE" -eq 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
-pid=`pgrep -u root -f clixon_backend`
+pid=$(pgrep -u root -f clixon_backend)
if [ -z "$pid" ]; then
err "backend already dead"
fi
# kill backend
-stop_backend -f $cfg
+stop_backend -f "$cfg"
sudo pkill -u root -f clixon_backend
-rm -rf $dir
+rm -rf "$dir"
diff --git a/yang/clixon/Makefile.in b/yang/clixon/Makefile.in
index c982ddc2..87d5e65b 100644
--- a/yang/clixon/Makefile.in
+++ b/yang/clixon/Makefile.in
@@ -42,7 +42,7 @@ datarootdir = @datarootdir@
YANG_INSTALLDIR = @YANG_INSTALLDIR@
YANGSPECS = clixon-config@2019-06-05.yang
-YANGSPECS += clixon-lib@2019-06-05.yang
+YANGSPECS += clixon-lib@2019-08-13.yang
YANGSPECS += clixon-rfc5277@2008-07-01.yang
YANGSPECS += clixon-xml-changelog@2019-03-21.yang
diff --git a/yang/clixon/clixon-lib@2019-06-05.yang b/yang/clixon/clixon-lib@2019-08-13.yang
similarity index 67%
rename from yang/clixon/clixon-lib@2019-06-05.yang
rename to yang/clixon/clixon-lib@2019-08-13.yang
index 370819c8..343ca634 100644
--- a/yang/clixon/clixon-lib@2019-06-05.yang
+++ b/yang/clixon/clixon-lib@2019-08-13.yang
@@ -40,6 +40,10 @@ module clixon-lib {
***** END LICENSE BLOCK *****";
+ revision 2019-08-13 {
+ description
+ "get-state added for restconf content=nonconfig internal rpc";
+ }
revision 2019-06-05 {
description
"ping rpc added for liveness";
@@ -59,4 +63,34 @@ module clixon-lib {
rpc ping {
description "Check aliveness of backend daemon.";
}
+ rpc get-state {
+ description
+ "Retrieve device state information only. This is a clixon extension
+ to ietf-netconf to implement RESTCONF GET with attribute
+ content=nonconfig.
+ The reason is that netconf only has and neither
+ which retrieves state only";
+
+ reference "RFC 8040 4.8.1";
+
+ input {
+ anyxml filter {
+ description
+ "This parameter specifies the portion of the system
+ configuration and state data to retrieve.";
+ nc:get-filter-element-attributes;
+ }
+ }
+
+ output {
+ anyxml data {
+ description
+ "Copy of the running datastore subset and/or state
+ data that matched the filter criteria (if any).
+ An empty data container indicates that the request did not
+ produce any results.";
+ }
+ }
+ }
+
}