* RESTCONF "content" query parameter supported

* New clixon-lib@2019-08-13.yang revision
* Bugfix: If `ietf-netconf.yang` was imported from any yang module, client/backend communication stops working.
This commit is contained in:
Olof hagsand 2019-08-13 13:21:11 +02:00
parent 48022e57b9
commit 8b7b7b0f60
20 changed files with 507 additions and 62 deletions

View file

@ -6,6 +6,7 @@
* Restconf RFC 8040 increased feature compliance * Restconf RFC 8040 increased feature compliance
* RESTCONF PATCH (plain patch) is being implemented according to RFC 8040 Section 4.6.1 * 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 * 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 * RESTCONF "insert" and "point" query parameters supported
* Applies to ordered-by-user leaf and leaf-lists * Applies to ordered-by-user leaf and leaf-lists
* RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns: * 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. * 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) ### 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 * Netconf edit-config "operation" attribute namespace check is enforced
* This is enforced: `<a xmlns="uri:example" nc:operation="merge" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"> * This is enforced: `<a xmlns="uri:example" nc:operation="merge" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
* This was previously allowed: `<a xmlns="uri:example" operation="merge"> * This was previously allowed: `<a xmlns="uri:example" operation="merge">
@ -51,6 +54,8 @@
* pseudo-plugin added, to enable callbacks also for main programs. Useful for extensions * pseudo-plugin added, to enable callbacks also for main programs. Useful for extensions
### Corrected Bugs ### 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 * RESTCONF JSON identity had wrong namespace in sub-objetcs
* Showed if you GET an object with JSON encoding that have identities * Showed if you GET an object with JSON encoding that have identities
* Fixed Segv in nacm write when MERGE and creating object * Fixed Segv in nacm write when MERGE and creating object

View file

@ -942,6 +942,94 @@ from_client_get(clicon_handle h,
return retval; return retval;
} }
/*! Retrieve device state information only
*
* This is a CLIXON specific RPC
* @param[in] h Clicon handle
* @param[in] xe Request: <rpc><xn></rpc>
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] arg client-entry
* @param[in] regarg User argument given at rpc_callback_register()
* @retval 0 OK
* @retval -1 Error
*
* @see from_client_get
*/
static int
from_client_get_state(clicon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
int retval = -1;
cxobj *xfilter;
char *xpath = "/";
cxobj *xret = NULL;
int ret;
cxobj **xvec = NULL;
size_t xlen;
cxobj *xnacm = NULL;
char *username;
cvec *nsc = NULL; /* Create a netconf namespace context from filter */
username = clicon_username_get(h);
if ((xfilter = xml_find(xe, "filter")) != NULL){
if ((xpath = xml_find_value(xfilter, "select"))==NULL)
xpath="/";
/* Create namespace context for xpath from <filter>
* The set of namespace declarations are those in scope on the
* <filter> 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, "<rpc-reply>"); /* OK */
if (xret==NULL)
cprintf(cbret, "<data/>");
else{
if (xml_name_set(xret, "data") < 0)
goto done;
if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0)
goto done;
}
cprintf(cbret, "</rpc-reply>");
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. /*! Request graceful termination of a NETCONF session.
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] xe Request: <rpc><xn></rpc> * @param[in] xe Request: <rpc><xn></rpc>
@ -1283,7 +1371,7 @@ from_client_msg(clicon_handle h,
goto reply; /* Dont quit here on user callbacks */ goto reply; /* Dont quit here on user callbacks */
} }
if (ret == 0){ /* not handled by callback */ 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 done;
goto reply; goto reply;
} }
@ -1424,6 +1512,9 @@ backend_rpc_init(clicon_handle h)
"urn:ietf:params:xml:ns:netmod:notification", "create-subscription") < 0) "urn:ietf:params:xml:ns:netmod:notification", "create-subscription") < 0)
goto done; goto done;
/* Clixon RPC */ /* 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, if (rpc_callback_register(h, from_client_debug, NULL,
"http://clicon.org/lib", "debug") < 0) "http://clicon.org/lib", "debug") < 0)
goto done; goto done;

View file

@ -570,6 +570,12 @@ main(int argc,
if (xmldb_connect(h) < 0) if (xmldb_connect(h) < 0)
goto done; 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 */ /* Create top-level yang spec and store as option */
if ((yspec = yspec_new()) == NULL) if ((yspec = yspec_new()) == NULL)
goto done; goto done;
@ -601,7 +607,8 @@ main(int argc,
/* Load yang module library, RFC7895 */ /* Load yang module library, RFC7895 */
if (yang_modules_init(h) < 0) if (yang_modules_init(h) < 0)
goto done; 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) if (netconf_module_load(h) < 0)
goto done; goto done;
/* Load yang restconf module */ /* Load yang restconf module */

View file

@ -472,6 +472,12 @@ main(int argc, char **argv)
clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0) clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
goto done; 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 */ /* Create top-level and store as option */
if ((yspec = yspec_new()) == NULL) if ((yspec = yspec_new()) == NULL)
goto done; goto done;

View file

@ -484,6 +484,12 @@ main(int argc,
/* Access the remaining argv/argc options (after --) w clicon-argv_get() */ /* Access the remaining argv/argc options (after --) w clicon-argv_get() */
clicon_argv_set(h, argv0, argc, argv); 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 */ /* Create top-level yang spec and store as option */
if ((yspec = yspec_new()) == NULL) if ((yspec = yspec_new()) == NULL)
goto done; goto done;

View file

@ -143,6 +143,7 @@ static const map_str2int http_reason_phrase_map[] = {
}; };
/* See RFC 8040 /* See RFC 8040
* @see restconf_media_str2int
*/ */
static const map_str2int http_media_map[] = { static const map_str2int http_media_map[] = {
{"application/yang-data+xml", YANG_DATA_XML}, {"application/yang-data+xml", YANG_DATA_XML},
@ -152,6 +153,16 @@ static const map_str2int http_media_map[] = {
{NULL, -1} {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 int
restconf_err2code(char *tag) restconf_err2code(char *tag)
{ {
@ -176,6 +187,18 @@ restconf_media_int2str(restconf_media media)
return clicon_int2str(http_media_map, 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 /*! Return media_in from Content-Type, -1 if not found or unrecognized
* @note media-type syntax does not support parameters * @note media-type syntax does not support parameters
* @see RFC7231 Sec 3.1.1.1 for media-type syntax type: * @see RFC7231 Sec 3.1.1.1 for media-type syntax type:

View file

@ -57,6 +57,16 @@ enum restconf_media{
}; };
typedef enum restconf_media 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) * 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 restconf_media restconf_media_str2int(char *media);
const char *restconf_media_int2str(restconf_media media); const char *restconf_media_int2str(restconf_media media);
restconf_media restconf_content_type(FCGX_Request *r); 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_badrequest(FCGX_Request *r);
int restconf_unauthorized(FCGX_Request *r); int restconf_unauthorized(FCGX_Request *r);
int restconf_forbidden(FCGX_Request *r); int restconf_forbidden(FCGX_Request *r);

View file

@ -695,6 +695,12 @@ main(int argc,
/* Access the remaining argv/argc options (after --) w clicon-argv_get() */ /* Access the remaining argv/argc options (after --) w clicon-argv_get() */
clicon_argv_set(h, argv0, argc, argv); 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 */ /* Create top-level yang spec and store as option */
if ((yspec = yspec_new()) == NULL) if ((yspec = yspec_new()) == NULL)
goto done; goto done;

View file

@ -114,12 +114,31 @@ api_data_get2(clicon_handle h,
int ret; int ret;
char *namespace = NULL; char *namespace = NULL;
cvec *nsc = NULL; cvec *nsc = NULL;
char *str;
query_content content = CONTENT_ALL;
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
if ((yspec = clicon_dbspec_yang(h)) == NULL){ if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC"); clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done; 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",
"<bad-attribute>content</bad-attribute>", "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) if ((cbpath = cbuf_new()) == NULL)
goto done; goto done;
cprintf(cbpath, "/"); cprintf(cbpath, "/");
@ -144,7 +163,21 @@ api_data_get2(clicon_handle h,
* xpath expressions */ * xpath expressions */
if ((nsc = xml_nsctx_init(NULL, namespace)) == NULL) if ((nsc = xml_nsctx_init(NULL, namespace)) == NULL)
goto done; 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) if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0)
goto done; goto done;
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ if ((xe = xpath_first(xerr, "rpc-error")) == NULL){

View file

@ -31,6 +31,12 @@
***** END LICENSE BLOCK ***** ***** 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 <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -284,7 +290,7 @@ example_copy_extra(clicon_handle h, /* Clicon handle */
type string; 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 int
example_statedata(clicon_handle h, example_statedata(clicon_handle h,
@ -300,9 +306,12 @@ example_statedata(clicon_handle h,
cxobj *xt = NULL; cxobj *xt = NULL;
char *name; char *name;
cvec *nsc1 = NULL; cvec *nsc1 = NULL;
cvec *nsc2 = NULL;
yang_stmt *yspec = NULL;
if (!_state) if (!_state)
goto ok; goto ok;
yspec = clicon_dbspec_yang(h);
/* Example of statedata, in this case merging state data with /* Example of statedata, in this case merging state data with
* state information. In this case adding dummy interface operation state * state information. In this case adding dummy interface operation state
@ -311,6 +320,7 @@ example_statedata(clicon_handle h,
if (xmldb_get0(h, "running", nsc, xpath, 1, &xt, NULL) < 0) if (xmldb_get0(h, "running", nsc, xpath, 1, &xt, NULL) < 0)
goto done; goto done;
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 /* Here a separate namespace context nsc1 is created. The original nsc
* created by the system cannot be used trivially, since we dont know * created by the system cannot be used trivially, since we dont know
* the prefixes, although we could by a complex mechanism find the prefix * the prefixes, although we could by a complex mechanism find the prefix
@ -331,17 +341,54 @@ example_statedata(clicon_handle h,
if (xml_parse_string(cbuf_get(cb), NULL, &xstate) < 0) if (xml_parse_string(cbuf_get(cb), NULL, &xstate) < 0)
goto done; 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("<state xmlns=\"urn:example:clixon\">" if (xml_parse_string("<state xmlns=\"urn:example:clixon\">"
"<op>42</op>" "<op>42</op>"
"<op>41</op>" "<op>41</op>"
"<op>43</op>" /* should not be ordered */ "<op>43</op>" /* should not be ordered */
"</state>", NULL, &xstate) < 0) "</state>", NULL, &xstate) < 0)
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; 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, "<events xmlns=\"urn:example:events\">");
for (i=0; i<xlen; i++){
name = xml_body(xvec[i]);
c = 0;
for (j=0; j<strlen(name); j++)
c ^= name[j];
cprintf(cb, "<event><name>%s</name><event-count>%d</event-count></event>", name, c);
}
cprintf(cb, "</events>");
if (xml_parse_string(cbuf_get(cb), NULL, &xstate) < 0)
goto done;
}
}
ok: ok:
retval = 0; retval = 0;
done: done:
if (nsc1) if (nsc1)
xml_nsctx_free(nsc1); xml_nsctx_free(nsc1);
if (nsc2)
xml_nsctx_free(nsc2);
if (xt) if (xt)
xml_free(xt); xml_free(xt);
if (cb) if (cb)
@ -389,7 +436,6 @@ example_extension(clicon_handle h,
return retval; return retval;
} }
/*! Testcase upgrade function moving interfaces-state to interfaces /*! Testcase upgrade function moving interfaces-state to interfaces
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] xn XML tree to be updated * @param[in] xn XML tree to be updated

View file

@ -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_too_big(cbuf *cb, char *type, char *message);
int netconf_missing_attribute(cbuf *cb, char *type, char *info, 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(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_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(cbuf *cb, char *type, char *element, char *message);
int netconf_missing_element_xml(cxobj **xret, 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_data_not_unique_xml(cxobj **xret, cxobj *x, cvec *cvk);
int netconf_minmax_elements_xml(cxobj **xret, cxobj *x, int max); int netconf_minmax_elements_xml(cxobj **xret, cxobj *x, int max);
int netconf_trymerge(cxobj *x, yang_stmt *yspec, cxobj **xret); int netconf_trymerge(cxobj *x, yang_stmt *yspec, cxobj **xret);
int netconf_module_features(clicon_handle h);
int netconf_module_load(clicon_handle h); int netconf_module_load(clicon_handle h);
char *netconf_db_find(cxobj *xn, char *name); char *netconf_db_find(cxobj *xn, char *name);
int netconf_err2cb(cxobj *xerr, cbuf **cberr); int netconf_err2cb(cxobj *xerr, cbuf **cberr);

View file

@ -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_lock(clicon_handle h, char *db);
int clicon_rpc_unlock(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(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_close_session(clicon_handle h);
int clicon_rpc_kill_session(clicon_handle h, int session_id); int clicon_rpc_kill_session(clicon_handle h, int session_id);
int clicon_rpc_validate(clicon_handle h, char *db); int clicon_rpc_validate(clicon_handle h, char *db);

View file

@ -272,33 +272,66 @@ netconf_bad_attribute(cbuf *cb,
char *message) char *message)
{ {
int retval = -1; int retval = -1;
cxobj *xret = NULL;
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; char *encstr = NULL;
if (cprintf(cb, "<rpc-reply><rpc-error>" if (*xret == NULL){
"<error-type>%s</error-type>" 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, "<error-type>%s</error-type>"
"<error-tag>bad-attribute</error-tag>" "<error-tag>bad-attribute</error-tag>"
"<error-info>%s</error-info>" "<error-info>%s</error-info>"
"<error-severity>error</error-severity>", "<error-severity>error</error-severity>", type, info) < 0)
type, info) <0) goto done;
goto err;
if (message){ if (message){
if (xml_chardata_encode(&encstr, "%s", message) < 0) if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done; goto done;
if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0) if (xml_parse_va(&xerr, NULL, "<error-message>%s</error-message>",
goto err; encstr) < 0)
goto done;
} }
if (cprintf(cb, "</rpc-error></rpc-reply>") <0)
goto err;
retval = 0; retval = 0;
done: done:
if (encstr) if (encstr)
free(encstr); free(encstr);
return retval; return retval;
err:
clicon_err(OE_XML, errno, "cprintf");
goto done;
} }
/*! Create Netconf unknwon-attribute error XML tree according to RFC 6241 App A /*! Create Netconf unknwon-attribute error XML tree according to RFC 6241 App A
* *
* An unexpected attribute is present. * An unexpected attribute is present.
@ -462,6 +495,7 @@ netconf_bad_element(cbuf *cb,
xml_free(xret); xml_free(xret);
return retval; return retval;
} }
int int
netconf_bad_element_xml(cxobj **xret, netconf_bad_element_xml(cxobj **xret,
char *type, char *type,
@ -1208,14 +1242,21 @@ netconf_trymerge(cxobj *x,
} }
/*! Load ietf netconf yang module and set enabled features /*! 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) * candidate (8.3)
* validate (8.6) * validate (8.6)
* startup (8.7) * startup (8.7)
* xpath (8.9) * xpath (8.9)
* @see netconf_module_load that is called later
*/ */
int int
netconf_module_load(clicon_handle h) netconf_module_features(clicon_handle h)
{ {
int retval = -1; int retval = -1;
cxobj *xc; cxobj *xc;
@ -1233,6 +1274,24 @@ netconf_module_load(clicon_handle h)
goto done; goto done;
if (xml_parse_string("<CLICON_FEATURE>ietf-netconf:xpath</CLICON_FEATURE>", yspec, &xc) < 0) if (xml_parse_string("<CLICON_FEATURE>ietf-netconf:xpath</CLICON_FEATURE>", yspec, &xc) < 0)
goto done; 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 */ /* Load yang spec */
if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec)< 0) if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec)< 0)
goto done; goto done;

View file

@ -615,6 +615,85 @@ clicon_rpc_get(clicon_handle h,
return retval; 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 <config> or <rpc-error>.
* @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, "<rpc");
if ((username = clicon_username_get(h)) != NULL)
cprintf(cb, " username=\"%s\"", username);
if (namespace)
cprintf(cb, " xmlns:nc=\"%s\"", NETCONF_BASE_NAMESPACE);
cprintf(cb, "><cl:get-state xmlns:cl=\"http://clicon.org/lib\">");
if (xpath && strlen(xpath)) {
if (namespace)
cprintf(cb, "<nc:filter nc:type=\"xpath\" nc:select=\"%s\" xmlns=\"%s\"/>",
xpath, namespace);
else /* If xpath != /, this will probably yield an error later */
cprintf(cb, "<filter type=\"xpath\" select=\"%s\"/>", xpath);
}
cprintf(cb, "</cl:get-state></rpc>");
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 /*! Close a (user) session
* @param[in] h CLICON handle * @param[in] h CLICON handle
* @retval 0 OK * @retval 0 OK

View file

@ -3196,7 +3196,7 @@ xml_merge(cxobj *x0,
goto done; goto done;
if (ymod == NULL){ if (ymod == NULL){
if (reason && 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"); clicon_err(OE_UNIX, errno, "strdup");
goto done; goto done;
} }

View file

@ -74,12 +74,12 @@ expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/r
# Should be alphabetically ordered # Should be alphabetically ordered
new "restconf get restconf/operations. RFC8040 3.3.2 (json)" 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)" new "restconf get restconf/operations. RFC8040 3.3.2 (xml)"
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations)
expect='<operations><client-rpc xmlns="urn:example:clixon"/><empty xmlns="urn:example:clixon"/><optional xmlns="urn:example:clixon"/><example xmlns="urn:example:clixon"/><debug xmlns="http://clicon.org/lib"/><ping xmlns="http://clicon.org/lib"/><get-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><copy-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><delete-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><lock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><unlock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><close-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><kill-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><commit xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><discard-changes xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><validate xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><create-subscription xmlns="urn:ietf:params:xml:ns:netmod:notification"/></operations>' expect='<operations><client-rpc xmlns="urn:example:clixon"/><empty xmlns="urn:example:clixon"/><optional xmlns="urn:example:clixon"/><example xmlns="urn:example:clixon"/><debug xmlns="http://clicon.org/lib"/><ping xmlns="http://clicon.org/lib"/><get-state xmlns="http://clicon.org/lib"/><get-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><copy-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><delete-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><lock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><unlock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><close-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><kill-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><commit xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><discard-changes xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><validate xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><create-subscription xmlns="urn:ietf:params:xml:ns:netmod:notification"/></operations>'
match=`echo $ret | grep -EZo "$expect"` match=`echo $ret | grep -EZo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
err "$expect" "$ret" err "$expect" "$ret"

View file

@ -8,6 +8,7 @@ APPNAME=example
cfg=$dir/conf.xml cfg=$dir/conf.xml
fjukebox=$dir/example-jukebox.yang fjukebox=$dir/example-jukebox.yang
fcontent=$dir/example-events.yang
# A "system" module as defined in B.2.4 # A "system" module as defined in B.2.4
cat <<EOF > $dir/example-system.yang cat <<EOF > $dir/example-system.yang
@ -38,6 +39,25 @@ cat <<EOF > $cfg
</clixon-config> </clixon-config>
EOF EOF
# yang B.3.1. "content" Parameter
cat <<EOF > $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) # Common Jukebox spec (fjukebox must be set)
. ./jukebox.sh . ./jukebox.sh
@ -50,8 +70,8 @@ if [ $BE -ne 0 ]; then
err err
fi fi
sudo pkill clixon_backend # to be sure sudo pkill clixon_backend # to be sure
new "start backend -s init -f $cfg" new "start backend -s init -f $cfg -- -s"
start_backend -s init -f $cfg start_backend -s init -f "$cfg" -- -s
fi fi
new "kill old restconf daemon" 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 # This just catches the header and the jukebox module, the RFC has foo and bar which
# seems wrong to recreate # seems wrong to recreate
new "B.1.2. Retrieve the Server Module Information" 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" new "B.1.3. Retrieve the Server Capability Information"
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/capabilities)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+xml" 'Cache-Control: no-cache' '<capabilities xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf-monitoring"><capability>urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit</capability></capabilities>' expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/capabilities)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+xml" 'Cache-Control: no-cache' '<capabilities xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf-monitoring"><capability>urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit</capability></capabilities>'
@ -129,6 +149,22 @@ expectpart "$(curl -si -X PATCH http://localhost/restconf/data/example-jukebox:j
new "B.2.5. Check edit" new "B.2.5. Check edit"
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Nick%20Cave%20and%20the%20Bad%20Seeds -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<artist xmlns="http://example.com/ns/example-jukebox"><name>Nick Cave and the Bad Seeds</name><album><name>Tender Prey</name><year>1988</year></album><album><name>The Good Son</name><year>1990</year></album></artist>' expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Nick%20Cave%20and%20the%20Bad%20Seeds -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<artist xmlns="http://example.com/ns/example-jukebox"><name>Nick Cave and the Bad Seeds</name><album><name>Tender Prey</name><year>1988</year></album><album><name>The Good Son</name><year>1990</year></album></artist>'
# 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" new "restconf DELETE whole datastore"
expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 "" 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 if false; then # NYI
new "B.2.2. Detect Datastore Resource Entity-Tag Change" # XXX done except entity-changed 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.2. "depth" Parameter'
new 'B.3.3. "fields" Parameter' new 'B.3.3. "fields" Parameter'
new 'B.3.6. "filter" Parameter' new 'B.3.6. "filter" Parameter'

View file

@ -2,7 +2,7 @@
# Yang test: multi-keys and empty type # Yang test: multi-keys and empty type
# Magic line must be first in script (see README.md) # 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 APPNAME=example
@ -143,14 +143,14 @@ EOF
new "test params: -f $cfg" new "test params: -f $cfg"
if [ $BE -ne 0 ]; then if [ "$BE" -ne 0 ]; then
new "kill old backend" new "kill old backend"
sudo clixon_backend -zf $cfg sudo clixon_backend -zf "$cfg"
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
err err
fi fi
new "start backend -s init -f $cfg" new "start backend -s init -f $cfg"
start_backend -s init -f $cfg start_backend -s init -f "$cfg"
new "waiting" new "waiting"
wait_backend wait_backend
@ -280,18 +280,18 @@ expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></sou
new "netconf submodule discard-changes" new "netconf submodule discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
if [ $BE -eq 0 ]; then if [ "$BE" -eq 0 ]; then
exit # BE exit # BE
fi fi
new "Kill backend" new "Kill backend"
# Check if premature kill # Check if premature kill
pid=`pgrep -u root -f clixon_backend` pid=$(pgrep -u root -f clixon_backend)
if [ -z "$pid" ]; then if [ -z "$pid" ]; then
err "backend already dead" err "backend already dead"
fi fi
# kill backend # kill backend
stop_backend -f $cfg stop_backend -f "$cfg"
sudo pkill -u root -f clixon_backend sudo pkill -u root -f clixon_backend
rm -rf $dir rm -rf "$dir"

View file

@ -42,7 +42,7 @@ datarootdir = @datarootdir@
YANG_INSTALLDIR = @YANG_INSTALLDIR@ YANG_INSTALLDIR = @YANG_INSTALLDIR@
YANGSPECS = clixon-config@2019-06-05.yang 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-rfc5277@2008-07-01.yang
YANGSPECS += clixon-xml-changelog@2019-03-21.yang YANGSPECS += clixon-xml-changelog@2019-03-21.yang

View file

@ -40,6 +40,10 @@ module clixon-lib {
***** END LICENSE BLOCK *****"; ***** END LICENSE BLOCK *****";
revision 2019-08-13 {
description
"get-state added for restconf content=nonconfig internal rpc";
}
revision 2019-06-05 { revision 2019-06-05 {
description description
"ping rpc added for liveness"; "ping rpc added for liveness";
@ -59,4 +63,34 @@ module clixon-lib {
rpc ping { rpc ping {
description "Check aliveness of backend daemon."; 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 <get> and <get-config> 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.";
}
}
}
} }