* 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:
parent
48022e57b9
commit
8b7b7b0f60
20 changed files with 507 additions and 62 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 */
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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){
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue