restconf RPC
This commit is contained in:
parent
d0660bf611
commit
fd91bb2933
17 changed files with 371 additions and 109 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -3,6 +3,8 @@
|
||||||
## Upcoming 3.3.2
|
## Upcoming 3.3.2
|
||||||
|
|
||||||
### Known issues
|
### Known issues
|
||||||
|
* Please use text datastore, key-value datastore no up-to-date
|
||||||
|
* Restconf RPC does not encode output correct
|
||||||
|
|
||||||
### Major changes:
|
### Major changes:
|
||||||
* Added support for YANG anyxml.
|
* Added support for YANG anyxml.
|
||||||
|
|
@ -50,13 +52,12 @@ If you submit "nopresence" without a leaf, it will automatically be removed:
|
||||||
</nopresence>
|
</nopresence>
|
||||||
```
|
```
|
||||||
|
|
||||||
* Added YANG RPC support for netconf and CLI. With example rpc documentation and testcase. This replaces the previous "downcall" mechanism.
|
* Added YANG RPC support for netconf, restconf and CLI. With example rpc documentation and testcase. This replaces the previous "downcall" mechanism.
|
||||||
* This means you can make netconf rpc calls as defined by YANG.
|
* This means you can make netconf/restconf rpc calls
|
||||||
* However you need to register an RPC backend callback using the backend_rpc_cb_register() function. See documentation and example for more details.
|
* However you need to register an RPC backend callback using the backend_rpc_cb_register() function. See documentation and example for more details.
|
||||||
* Note that RESTCONF PUT for RCP calls is not yet supported.
|
|
||||||
* Example, the following YANG RPC definition enables you to run a netconf rpc.
|
* Example, the following YANG RPC definition enables you to run a netconf rpc.
|
||||||
```
|
```
|
||||||
YANG:
|
YANG:
|
||||||
rpc myrpc {
|
rpc myrpc {
|
||||||
input {
|
input {
|
||||||
leaf name {
|
leaf name {
|
||||||
|
|
@ -64,8 +65,10 @@ If you submit "nopresence" without a leaf, it will automatically be removed:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NETCONF:
|
NETCONF:
|
||||||
<rpc><myrpc><name>hello</name><rpc>
|
<rpc><myrpc><name>hello</name><rpc>
|
||||||
|
RESTCONF:
|
||||||
|
curl -sS -X POST -d {"input":{"name":"hello"}} http://localhost/restconf/operations/myroute'
|
||||||
```
|
```
|
||||||
|
|
||||||
* Enhanced leafref functionality:
|
* Enhanced leafref functionality:
|
||||||
|
|
|
||||||
|
|
@ -236,7 +236,7 @@ cli_dbxml(clicon_handle h,
|
||||||
if ((xtop = xml_new("config", NULL)) == NULL)
|
if ((xtop = xml_new("config", NULL)) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
xbot = xtop;
|
xbot = xtop;
|
||||||
if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0)
|
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if ((xa = xml_new("operation", xbot)) == NULL)
|
if ((xa = xml_new("operation", xbot)) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,7 @@ expand_dbvar(void *h,
|
||||||
if ((xtop = xml_new("config", NULL)) == NULL)
|
if ((xtop = xml_new("config", NULL)) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
xbot = xtop;
|
xbot = xtop;
|
||||||
if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0)
|
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
/* Special case for leafref. Detect leafref via Yang-type,
|
/* Special case for leafref. Detect leafref via Yang-type,
|
||||||
* Get Yang path element, tentatively add the new syntax to the whole
|
* Get Yang path element, tentatively add the new syntax to the whole
|
||||||
|
|
|
||||||
|
|
@ -196,27 +196,6 @@ catch:
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Struct to carry info into and out of ys_find_rpc callback
|
|
||||||
*/
|
|
||||||
typedef struct {
|
|
||||||
char *name; /* name of rpc */
|
|
||||||
yang_stmt *yrpc; /* matching yang statement */
|
|
||||||
} find_rpc_arg;
|
|
||||||
|
|
||||||
/*! Check yang rpc statement, return yang rpc statement if found
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
ys_find_rpc(yang_stmt *ys,
|
|
||||||
void *arg)
|
|
||||||
{
|
|
||||||
find_rpc_arg *fra = (find_rpc_arg*)arg;
|
|
||||||
|
|
||||||
if (strcmp(fra->name, ys->ys_argument) == 0){
|
|
||||||
fra->yrpc = ys;
|
|
||||||
return 1; /* handled */
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*! See if there is any callback registered for this tag
|
/*! See if there is any callback registered for this tag
|
||||||
*
|
*
|
||||||
|
|
@ -240,8 +219,7 @@ netconf_plugin_callbacks(clicon_handle h,
|
||||||
yang_stmt *yinput;
|
yang_stmt *yinput;
|
||||||
yang_stmt *youtput;
|
yang_stmt *youtput;
|
||||||
cxobj *xoutput;
|
cxobj *xoutput;
|
||||||
find_rpc_arg fra = {0,0};
|
cbuf *cb;
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (deps != NULL){
|
if (deps != NULL){
|
||||||
nreg = deps;
|
nreg = deps;
|
||||||
|
|
@ -259,13 +237,17 @@ netconf_plugin_callbacks(clicon_handle h,
|
||||||
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
if ((cb = cbuf_new()) == NULL){
|
||||||
|
clicon_err(OE_UNIX, 0, "cbuf_new");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* create absolute path */
|
||||||
|
cprintf(cb, "/%s:%s", xml_namespace(xn), xml_name(xn));
|
||||||
/* Find yang rpc statement, return yang rpc statement if found */
|
/* Find yang rpc statement, return yang rpc statement if found */
|
||||||
fra.name = xml_name(xn);
|
if (yang_abs_schema_nodeid(yspec, cbuf_get(cb), &yrpc) < 0)
|
||||||
if ((ret = yang_apply((yang_node*)yspec, Y_RPC, ys_find_rpc, &fra)) < 0)
|
|
||||||
goto done;
|
goto done;
|
||||||
/* Check if found */
|
/* Check if found */
|
||||||
if (ret == 1){
|
if (yrpc != NULL){
|
||||||
yrpc = fra.yrpc;
|
|
||||||
if ((yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL)) != NULL){
|
if ((yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL)) != NULL){
|
||||||
xml_spec_set(xn, yinput); /* needed for xml_spec_populate */
|
xml_spec_set(xn, yinput); /* needed for xml_spec_populate */
|
||||||
if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yinput) < 0)
|
if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yinput) < 0)
|
||||||
|
|
@ -300,6 +282,8 @@ netconf_plugin_callbacks(clicon_handle h,
|
||||||
}
|
}
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
if (cb)
|
||||||
|
cbuf_free(cb);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@
|
||||||
resource ([RFC6415]) */
|
resource ([RFC6415]) */
|
||||||
#define RESTCONF_API_ROOT "/restconf/"
|
#define RESTCONF_API_ROOT "/restconf/"
|
||||||
|
|
||||||
/*! Generic REST method, GET, PUT, DELETE
|
/*! Generic REST method, GET, PUT, DELETE, etc
|
||||||
* @param[in] h CLIXON handle
|
* @param[in] h CLIXON handle
|
||||||
* @param[in] r Fastcgi request handle
|
* @param[in] r Fastcgi request handle
|
||||||
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft])
|
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft])
|
||||||
|
|
@ -120,6 +120,38 @@ api_data(clicon_handle h,
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! Operations REST method, POST
|
||||||
|
* @param[in] h CLIXON handle
|
||||||
|
* @param[in] r Fastcgi request handle
|
||||||
|
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft])
|
||||||
|
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
|
||||||
|
* @param[in] pi Offset, where to start pcvec
|
||||||
|
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||||
|
* @param[in] dvec Stream input data
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
api_operations(clicon_handle h,
|
||||||
|
FCGX_Request *r,
|
||||||
|
char *path,
|
||||||
|
cvec *pcvec,
|
||||||
|
int pi,
|
||||||
|
cvec *qvec,
|
||||||
|
char *data)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
char *request_method;
|
||||||
|
|
||||||
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
|
request_method = FCGX_GetParam("REQUEST_METHOD", r->envp);
|
||||||
|
clicon_debug(1, "%s method:%s", __FUNCTION__, request_method);
|
||||||
|
if (strcmp(request_method, "POST")==0)
|
||||||
|
retval = api_operation_post(h, r, path, pcvec, pi, qvec, data);
|
||||||
|
else
|
||||||
|
retval = notfound(r);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*! Process a FastCGI request
|
/*! Process a FastCGI request
|
||||||
* @param[in] r Fastcgi request handle
|
* @param[in] r Fastcgi request handle
|
||||||
*/
|
*/
|
||||||
|
|
@ -176,6 +208,9 @@ request_process(clicon_handle h,
|
||||||
|
|
||||||
if (strcmp(method, "data") == 0) /* restconf, skip /api/data */
|
if (strcmp(method, "data") == 0) /* restconf, skip /api/data */
|
||||||
retval = api_data(h, r, path, pcvec, 2, qvec, data);
|
retval = api_data(h, r, path, pcvec, 2, qvec, data);
|
||||||
|
else
|
||||||
|
if (strcmp(method, "operations") == 0) /* rpc */
|
||||||
|
retval = api_operations(h, r, path, pcvec, 2, qvec, data);
|
||||||
else if (strcmp(method, "test") == 0)
|
else if (strcmp(method, "test") == 0)
|
||||||
retval = test(r, 0);
|
retval = test(r, 0);
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -354,7 +354,7 @@ api_data_post(clicon_handle h,
|
||||||
if ((xtop = xml_new("config", NULL)) == NULL)
|
if ((xtop = xml_new("config", NULL)) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
xbot = xtop;
|
xbot = xtop;
|
||||||
if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0)
|
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
/* Parse input data as json into xml */
|
/* Parse input data as json into xml */
|
||||||
if (json_parse_str(data, &xdata) < 0){
|
if (json_parse_str(data, &xdata) < 0){
|
||||||
|
|
@ -462,7 +462,7 @@ api_data_put(clicon_handle h,
|
||||||
if ((xtop = xml_new("config", NULL)) == NULL)
|
if ((xtop = xml_new("config", NULL)) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
xbot = xtop;
|
xbot = xtop;
|
||||||
if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0)
|
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
/* Parse input data as json into xml */
|
/* Parse input data as json into xml */
|
||||||
if (json_parse_str(data, &xdata) < 0){
|
if (json_parse_str(data, &xdata) < 0){
|
||||||
|
|
@ -581,7 +581,7 @@ api_data_delete(clicon_handle h,
|
||||||
if ((xtop = xml_new("config", NULL)) == NULL)
|
if ((xtop = xml_new("config", NULL)) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
xbot = xtop;
|
xbot = xtop;
|
||||||
if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0)
|
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if ((xa = xml_new("operation", xbot)) == NULL)
|
if ((xa = xml_new("operation", xbot)) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -606,7 +606,7 @@ api_data_delete(clicon_handle h,
|
||||||
FCGX_FPrintF(r->out, "\r\n");
|
FCGX_FPrintF(r->out, "\r\n");
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
if (cbx)
|
if (cbx)
|
||||||
cbuf_free(cbx);
|
cbuf_free(cbx);
|
||||||
if (xtop)
|
if (xtop)
|
||||||
xml_free(xtop);
|
xml_free(xtop);
|
||||||
|
|
@ -614,3 +614,116 @@ api_data_delete(clicon_handle h,
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! REST operation POST method
|
||||||
|
* @param[in] h CLIXON handle
|
||||||
|
* @param[in] r Fastcgi request handle
|
||||||
|
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
|
||||||
|
* @param[in] pi Offset, where to start pcvec
|
||||||
|
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||||
|
* @param[in] data Stream input data
|
||||||
|
* @note We map post to edit-config create.
|
||||||
|
|
||||||
|
POST {+restconf}/operations/<operation>
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
api_operation_post(clicon_handle h,
|
||||||
|
FCGX_Request *r,
|
||||||
|
char *path,
|
||||||
|
cvec *pcvec,
|
||||||
|
int pi,
|
||||||
|
cvec *qvec,
|
||||||
|
char *data)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
int i;
|
||||||
|
char *oppath = path;
|
||||||
|
yang_stmt *yrpc = NULL;
|
||||||
|
yang_spec *yspec;
|
||||||
|
yang_stmt *yinput;
|
||||||
|
cxobj *xdata = NULL;
|
||||||
|
cxobj *xret = NULL;
|
||||||
|
cbuf *cbx = NULL;
|
||||||
|
cxobj *xtop = NULL; /* xpath root */
|
||||||
|
cxobj *xbot = NULL;
|
||||||
|
yang_node *y = NULL;
|
||||||
|
cxobj *xinput;
|
||||||
|
cxobj *x;
|
||||||
|
cxobj **vec = NULL;
|
||||||
|
|
||||||
|
clicon_debug(1, "%s json:\"%s\"", __FUNCTION__, data);
|
||||||
|
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||||
|
clicon_err(OE_FATAL, 0, "No DB_SPEC");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
for (i=0; i<pi; i++)
|
||||||
|
oppath = index(oppath+1, '/');
|
||||||
|
/* Find yang rpc statement, return yang rpc statement if found */
|
||||||
|
if (yang_abs_schema_nodeid(yspec, oppath, &yrpc) < 0)
|
||||||
|
goto done;
|
||||||
|
if (yrpc == NULL){
|
||||||
|
retval = notfound(r);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* Create an xml message:
|
||||||
|
* <"rpc"><operation><input-args>...
|
||||||
|
* eg <rpc><fib-route><name>
|
||||||
|
*/
|
||||||
|
/* Create config top-of-tree */
|
||||||
|
if ((xtop = xml_new("rpc", NULL)) == NULL)
|
||||||
|
goto done;
|
||||||
|
xbot = xtop;
|
||||||
|
if (api_path2xml(oppath, yspec, xtop, 1, &xbot, &y) < 0)
|
||||||
|
goto done;
|
||||||
|
if (data){
|
||||||
|
/* Parse input data as json into xml */
|
||||||
|
if (json_parse_str(data, &xdata) < 0){
|
||||||
|
clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* xdata should have format <top><input> */
|
||||||
|
if ((xinput = xpath_first(xdata, "/input")) != NULL){
|
||||||
|
/* Add all input under <rpc>path */
|
||||||
|
x = NULL;
|
||||||
|
while ((x = xml_child_each(xinput, x, -1)) != NULL)
|
||||||
|
if (xml_addsub(xbot, x) < 0)
|
||||||
|
goto done;
|
||||||
|
if ((yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL)) != NULL){
|
||||||
|
xml_spec_set(xinput, yinput); /* needed for xml_spec_populate */
|
||||||
|
if (xml_apply(xinput, CX_ELMNT, xml_spec_populate, yinput) < 0)
|
||||||
|
goto done;
|
||||||
|
if (xml_apply(xinput, CX_ELMNT,
|
||||||
|
(xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
|
||||||
|
goto done;
|
||||||
|
if (xml_yang_validate_add(xinput, NULL) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0)
|
||||||
|
goto done;
|
||||||
|
if ((cbx = cbuf_new()) == NULL)
|
||||||
|
goto done;
|
||||||
|
/* Sanity check of outgoing XML */
|
||||||
|
FCGX_SetExitStatus(200, r->out); /* OK */
|
||||||
|
FCGX_FPrintF(r->out, "Content-Type: application/yang.data+json\r\n");
|
||||||
|
FCGX_FPrintF(r->out, "\r\n");
|
||||||
|
vec = xml_childvec_get(xret);
|
||||||
|
if (xml2json_cbuf_vec(cbx, vec, xml_child_nr(xret), 0) < 0)
|
||||||
|
goto done;
|
||||||
|
clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx));
|
||||||
|
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
|
||||||
|
FCGX_FPrintF(r->out, "\r\n\r\n");
|
||||||
|
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
||||||
|
if (xdata)
|
||||||
|
xml_free(xdata);
|
||||||
|
if (xret)
|
||||||
|
xml_free(xret);
|
||||||
|
if (cbx)
|
||||||
|
cbuf_free(cbx);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,4 +60,8 @@ int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path,
|
||||||
cvec *qvec, char *data);
|
cvec *qvec, char *data);
|
||||||
int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi);
|
int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi);
|
||||||
|
|
||||||
|
int api_operation_post(clicon_handle h, FCGX_Request *r,
|
||||||
|
char *path,
|
||||||
|
cvec *pcvec, int pi, cvec *qvec, char *data);
|
||||||
|
|
||||||
#endif /* _RESTCONF_METHODS_H_ */
|
#endif /* _RESTCONF_METHODS_H_ */
|
||||||
|
|
|
||||||
|
|
@ -321,7 +321,7 @@ get(char *dbname,
|
||||||
restval++;
|
restval++;
|
||||||
}
|
}
|
||||||
if (i == 1){ /* spec->module->node */
|
if (i == 1){ /* spec->module->node */
|
||||||
if ((y = yang_find_topnode(ys, name)) == NULL){
|
if ((y = yang_find_topnode(ys, name, 0)) == NULL){
|
||||||
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
|
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
@ -808,7 +808,7 @@ kv_put(xmldb_handle xh,
|
||||||
}
|
}
|
||||||
// clicon_log(LOG_WARNING, "%s", __FUNCTION__);
|
// clicon_log(LOG_WARNING, "%s", __FUNCTION__);
|
||||||
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){
|
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){
|
||||||
if ((ys = yang_find_topnode(yspec, xml_name(x))) == NULL){
|
if ((ys = yang_find_topnode(yspec, xml_name(x), 0)) == NULL){
|
||||||
clicon_err(OE_UNIX, errno, "No yang node found: %s", xml_name(x));
|
clicon_err(OE_UNIX, errno, "No yang node found: %s", xml_name(x));
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -663,7 +663,7 @@ text_modify_top(cxobj *x0,
|
||||||
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
|
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
|
||||||
x1cname = xml_name(x1c);
|
x1cname = xml_name(x1c);
|
||||||
/* Get yang spec of the child */
|
/* Get yang spec of the child */
|
||||||
if ((yc = yang_find_topnode(yspec, x1cname)) == NULL){
|
if ((yc = yang_find_topnode(yspec, x1cname, 0)) == NULL){
|
||||||
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,16 +38,6 @@
|
||||||
#ifndef _CLIXON_XML_MAP_H_
|
#ifndef _CLIXON_XML_MAP_H_
|
||||||
#define _CLIXON_XML_MAP_H_
|
#define _CLIXON_XML_MAP_H_
|
||||||
|
|
||||||
/*
|
|
||||||
* lvmap_xml op codes
|
|
||||||
*/
|
|
||||||
enum {
|
|
||||||
LVXML, /* a.b{x=1} -> <a><b>1 */
|
|
||||||
LVXML_VAL, /* a.b{x=1} -> <a><b><x>1 */
|
|
||||||
LVXML_VECVAL, /* key: a.b.0{x=1} -> <a><b><x>1</x></b></a> och */
|
|
||||||
LVXML_VECVAL2, /* key: a.b.0{x=1} -> <a><x>1</x></a> och */
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prototypes
|
* Prototypes
|
||||||
*/
|
*/
|
||||||
|
|
@ -73,7 +63,8 @@ int xml_non_config_data(cxobj *xt, void *arg);
|
||||||
int xml_spec_populate(cxobj *x, void *arg);
|
int xml_spec_populate(cxobj *x, void *arg);
|
||||||
int api_path2xpath_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath);
|
int api_path2xpath_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath);
|
||||||
int api_path2xpath(yang_spec *yspec, char *api_path, cbuf *xpath);
|
int api_path2xpath(yang_spec *yspec, char *api_path, cbuf *xpath);
|
||||||
int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop, cxobj **xpathp, yang_node **ypathp);
|
int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop,
|
||||||
|
int schemanode, cxobj **xpathp, yang_node **ypathp);
|
||||||
int xml_merge(cxobj *x0, cxobj *x1, yang_spec *yspec);
|
int xml_merge(cxobj *x0, cxobj *x1, yang_spec *yspec);
|
||||||
int yang_enum_int_value(cxobj *node, int32_t *val);
|
int yang_enum_int_value(cxobj *node, int32_t *val);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,7 @@ enum rfc_6020{
|
||||||
typedef struct yang_stmt yang_stmt; /* forward */
|
typedef struct yang_stmt yang_stmt; /* forward */
|
||||||
|
|
||||||
/*! Yang type cache. Yang type statements can cache all typedef info here
|
/*! Yang type cache. Yang type statements can cache all typedef info here
|
||||||
|
* @note unions not cached
|
||||||
*/
|
*/
|
||||||
struct yang_type_cache{
|
struct yang_type_cache{
|
||||||
int yc_options;
|
int yc_options;
|
||||||
|
|
@ -158,7 +159,7 @@ struct yang_stmt{
|
||||||
leaf, leaf-list, mandatory, fraction-digits */
|
leaf, leaf-list, mandatory, fraction-digits */
|
||||||
cvec *ys_cvec; /* List of stmt-specific variables
|
cvec *ys_cvec; /* List of stmt-specific variables
|
||||||
Y_RANGE: range_min, range_max */
|
Y_RANGE: range_min, range_max */
|
||||||
yang_type_cache *ys_typecache; /* If ys_keyword==Y_TYPE, cache all typedef data */
|
yang_type_cache *ys_typecache; /* If ys_keyword==Y_TYPE, cache all typedef data except unions */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -205,7 +206,8 @@ yang_spec *ys_spec(yang_stmt *ys);
|
||||||
yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix);
|
yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix);
|
||||||
yang_stmt *yang_find(yang_node *yn, int keyword, char *argument);
|
yang_stmt *yang_find(yang_node *yn, int keyword, char *argument);
|
||||||
yang_stmt *yang_find_datanode(yang_node *yn, char *argument);
|
yang_stmt *yang_find_datanode(yang_node *yn, char *argument);
|
||||||
yang_stmt *yang_find_topnode(yang_spec *ysp, char *name);
|
yang_stmt *yang_find_schemanode(yang_node *yn, char *argument);
|
||||||
|
yang_stmt *yang_find_topnode(yang_spec *ysp, char *name, int schemanode);
|
||||||
|
|
||||||
int yang_print(FILE *f, yang_node *yn);
|
int yang_print(FILE *f, yang_node *yn);
|
||||||
int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal);
|
int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal);
|
||||||
|
|
|
||||||
|
|
@ -974,7 +974,10 @@ clicon_xml2cbuf(cbuf *cb,
|
||||||
}
|
}
|
||||||
if (prettyprint && xml_body(x)==NULL)
|
if (prettyprint && xml_body(x)==NULL)
|
||||||
cprintf(cb, "%*s", level*XML_INDENT, "");
|
cprintf(cb, "%*s", level*XML_INDENT, "");
|
||||||
cprintf(cb, "</%s>", name);
|
cprintf(cb, "</");
|
||||||
|
if (xml_namespace(x))
|
||||||
|
cprintf(cb, "%s:", xml_namespace(x));
|
||||||
|
cprintf(cb, "%s>", name);
|
||||||
}
|
}
|
||||||
if (prettyprint)
|
if (prettyprint)
|
||||||
cprintf(cb, "\n");
|
cprintf(cb, "\n");
|
||||||
|
|
|
||||||
|
|
@ -644,7 +644,7 @@ xml_diff1(yang_stmt *ys,
|
||||||
while ((x1 = xml_child_each(xt1, x1, CX_ELMNT)) != NULL){
|
while ((x1 = xml_child_each(xt1, x1, CX_ELMNT)) != NULL){
|
||||||
name = xml_name(x1);
|
name = xml_name(x1);
|
||||||
if (ys->ys_keyword == Y_SPEC)
|
if (ys->ys_keyword == Y_SPEC)
|
||||||
y = yang_find_topnode((yang_spec*)ys, name);
|
y = yang_find_topnode((yang_spec*)ys, name, 0);
|
||||||
else
|
else
|
||||||
y = yang_find_datanode((yang_node*)ys, name);
|
y = yang_find_datanode((yang_node*)ys, name);
|
||||||
if (y == NULL){
|
if (y == NULL){
|
||||||
|
|
@ -754,7 +754,7 @@ xml_diff1(yang_stmt *ys,
|
||||||
while ((x2 = xml_child_each(xt2, x2, CX_ELMNT)) != NULL){
|
while ((x2 = xml_child_each(xt2, x2, CX_ELMNT)) != NULL){
|
||||||
name = xml_name(x2);
|
name = xml_name(x2);
|
||||||
if (ys->ys_keyword == Y_SPEC)
|
if (ys->ys_keyword == Y_SPEC)
|
||||||
y = yang_find_topnode((yang_spec*)ys, name);
|
y = yang_find_topnode((yang_spec*)ys, name, 0);
|
||||||
else
|
else
|
||||||
y = yang_find_datanode((yang_node*)ys, name);
|
y = yang_find_datanode((yang_node*)ys, name);
|
||||||
if (y == NULL){
|
if (y == NULL){
|
||||||
|
|
@ -1482,7 +1482,7 @@ xml_spec_populate(cxobj *x,
|
||||||
(yp = xml_spec(xp)) != NULL)
|
(yp = xml_spec(xp)) != NULL)
|
||||||
y = yang_find_datanode((yang_node*)yp, xml_name(x));
|
y = yang_find_datanode((yang_node*)yp, xml_name(x));
|
||||||
else
|
else
|
||||||
y = yang_find_topnode(yspec, name); /* still NULL for config */
|
y = yang_find_topnode(yspec, name, 0); /* still NULL for config */
|
||||||
#ifdef XXX_OBSOLETE /* Add validate elsewhere */
|
#ifdef XXX_OBSOLETE /* Add validate elsewhere */
|
||||||
if (y==NULL){
|
if (y==NULL){
|
||||||
clicon_err(OE_XML, EBADF, "yang spec not found for xml node '%s' xml parent name: '%s' yangspec:'%s']",
|
clicon_err(OE_XML, EBADF, "yang spec not found for xml node '%s' xml parent name: '%s' yangspec:'%s']",
|
||||||
|
|
@ -1544,7 +1544,7 @@ api_path2xpath_cvv(yang_spec *yspec,
|
||||||
clicon_debug(1, "[%d] cvname:%s", i, name);
|
clicon_debug(1, "[%d] cvname:%s", i, name);
|
||||||
clicon_debug(1, "cv2str%d", cv2str(cv, NULL, 0));
|
clicon_debug(1, "cv2str%d", cv2str(cv, NULL, 0));
|
||||||
if (i == offset){
|
if (i == offset){
|
||||||
if ((y = yang_find_topnode(yspec, name)) == NULL){
|
if ((y = yang_find_topnode(yspec, name, 0)) == NULL){
|
||||||
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
|
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
@ -1635,17 +1635,19 @@ api_path2xpath(yang_spec *yspec,
|
||||||
* @param[in] nvec Length of vec
|
* @param[in] nvec Length of vec
|
||||||
* @param[in] x0 Xpath tree so far
|
* @param[in] x0 Xpath tree so far
|
||||||
* @param[in] y0 Yang spec for x0
|
* @param[in] y0 Yang spec for x0
|
||||||
|
* @param[in] schemanode If set use schema nodes otherwise data nodes.
|
||||||
* @param[out] xpathp Resulting xml tree
|
* @param[out] xpathp Resulting xml tree
|
||||||
* @param[out] ypathp Yang spec matching xpathp
|
* @param[out] ypathp Yang spec matching xpathp
|
||||||
* @see api_path2xml
|
* @see api_path2xml
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
api_path2xml_vec(char **vec,
|
api_path2xml_vec(char **vec,
|
||||||
int nvec,
|
int nvec,
|
||||||
cxobj *x0,
|
cxobj *x0,
|
||||||
yang_node *y0,
|
yang_node *y0,
|
||||||
cxobj **xpathp,
|
int schemanode,
|
||||||
yang_node **ypathp)
|
cxobj **xpathp,
|
||||||
|
yang_node **ypathp)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
int j;
|
int j;
|
||||||
|
|
@ -1663,6 +1665,7 @@ api_path2xml_vec(char **vec,
|
||||||
int nvalvec;
|
int nvalvec;
|
||||||
cxobj *x = NULL;
|
cxobj *x = NULL;
|
||||||
yang_stmt *y = NULL;
|
yang_stmt *y = NULL;
|
||||||
|
char *local;
|
||||||
|
|
||||||
if ((name = vec[0]) == NULL){
|
if ((name = vec[0]) == NULL){
|
||||||
if (xpathp)
|
if (xpathp)
|
||||||
|
|
@ -1678,10 +1681,21 @@ api_path2xml_vec(char **vec,
|
||||||
if (percent_decode(restval_enc, &restval) < 0)
|
if (percent_decode(restval_enc, &restval) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (y0->yn_keyword == Y_SPEC) /* top-node */
|
/* Split into prefix and localname, ignore prefix for now */
|
||||||
y = yang_find_topnode((yang_spec*)y0, name);
|
if ((local = index(name, ':')) != NULL){
|
||||||
else
|
*local = '\0';
|
||||||
y = yang_find_datanode((yang_node*)y0, name);
|
local++;
|
||||||
|
name = local;
|
||||||
|
}
|
||||||
|
if (y0->yn_keyword == Y_SPEC){ /* top-node */
|
||||||
|
clicon_debug(1, "%s 1 %s", __FUNCTION__, name);
|
||||||
|
y = yang_find_topnode((yang_spec*)y0, name, schemanode);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
clicon_debug(1, "%s 2 %s", __FUNCTION__, name);
|
||||||
|
y = schemanode?yang_find_schemanode((yang_node*)y0, name):
|
||||||
|
yang_find_datanode((yang_node*)y0, name);
|
||||||
|
}
|
||||||
if (y == NULL){
|
if (y == NULL){
|
||||||
clicon_err(OE_YANG, errno, "No yang node found: %s", name);
|
clicon_err(OE_YANG, errno, "No yang node found: %s", name);
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -1756,6 +1770,7 @@ api_path2xml_vec(char **vec,
|
||||||
}
|
}
|
||||||
if (api_path2xml_vec(vec+1, nvec-1,
|
if (api_path2xml_vec(vec+1, nvec-1,
|
||||||
x, (yang_node*)y,
|
x, (yang_node*)y,
|
||||||
|
schemanode,
|
||||||
xpathp, ypathp) < 0)
|
xpathp, ypathp) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
@ -1769,14 +1784,17 @@ api_path2xml_vec(char **vec,
|
||||||
|
|
||||||
/*! Create xml tree from api-path
|
/*! Create xml tree from api-path
|
||||||
* @param[in] api_path API-path as defined in RFC 8040
|
* @param[in] api_path API-path as defined in RFC 8040
|
||||||
|
* @param[in] yspec Yang spec
|
||||||
|
* @param[in] schemanode If set use schema nodes otherwise data nodes.
|
||||||
* @param[out] xpathp Resulting xml tree
|
* @param[out] xpathp Resulting xml tree
|
||||||
* @param[out] ypathp Yang spec matching xpathp
|
* @param[out] ypathp Yang spec matching xpathp
|
||||||
* @see api_path2xml_vec
|
* @see api_path2xml_vec
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
api_path2xml(char *api_path,
|
api_path2xml(char *api_path,
|
||||||
yang_spec *yspec,
|
yang_spec *yspec,
|
||||||
cxobj *xpath,
|
cxobj *xpath,
|
||||||
|
int schemanode,
|
||||||
cxobj **xpathp,
|
cxobj **xpathp,
|
||||||
yang_node **ypathp)
|
yang_node **ypathp)
|
||||||
{
|
{
|
||||||
|
|
@ -1800,7 +1818,7 @@ api_path2xml(char *api_path,
|
||||||
}
|
}
|
||||||
nvec--; /* NULL-terminated */
|
nvec--; /* NULL-terminated */
|
||||||
if (api_path2xml_vec(vec+1, nvec,
|
if (api_path2xml_vec(vec+1, nvec,
|
||||||
xpath, (yang_node*)yspec,
|
xpath, (yang_node*)yspec, schemanode,
|
||||||
xpathp, ypathp) < 0)
|
xpathp, ypathp) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
@ -1984,7 +2002,7 @@ xml_merge(cxobj *x0,
|
||||||
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
|
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
|
||||||
x1cname = xml_name(x1c);
|
x1cname = xml_name(x1c);
|
||||||
/* Get yang spec of the child */
|
/* Get yang spec of the child */
|
||||||
if ((yc = yang_find_topnode(yspec, x1cname)) == NULL){
|
if ((yc = yang_find_topnode(yspec, x1cname, 0)) == NULL){
|
||||||
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
@ -2050,3 +2068,4 @@ yang_enum_int_value(cxobj *node,
|
||||||
done:
|
done:
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -453,18 +453,68 @@ yang_find_datanode(yang_node *yn,
|
||||||
return ysmatch;
|
return ysmatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Find 'top-node', eg first data node in all (sub)modules in a yang spec
|
/*! Find child schema node with matching argument (container, leaf, etc)
|
||||||
|
* @note XXX unify code with yang_find_datanode?
|
||||||
|
* @see yang_find_datanode
|
||||||
|
*/
|
||||||
|
yang_stmt *
|
||||||
|
yang_find_schemanode(yang_node *yn,
|
||||||
|
char *argument)
|
||||||
|
{
|
||||||
|
yang_stmt *ys = NULL;
|
||||||
|
yang_stmt *yc = NULL;
|
||||||
|
yang_stmt *ysmatch = NULL;
|
||||||
|
int i, j;
|
||||||
|
|
||||||
|
for (i=0; i<yn->yn_len; i++){
|
||||||
|
ys = yn->yn_stmt[i];
|
||||||
|
if (ys->ys_keyword == Y_CHOICE){ /* Look for its children */
|
||||||
|
for (j=0; j<ys->ys_len; j++){
|
||||||
|
yc = ys->ys_stmt[j];
|
||||||
|
if (yc->ys_keyword == Y_CASE) /* Look for its children */
|
||||||
|
ysmatch = yang_find_schemanode((yang_node*)yc, argument);
|
||||||
|
else
|
||||||
|
if (yang_schemanode(yc)){
|
||||||
|
if (argument == NULL)
|
||||||
|
ysmatch = yc;
|
||||||
|
else
|
||||||
|
if (yc->ys_argument && strcmp(argument, yc->ys_argument) == 0)
|
||||||
|
ysmatch = yc;
|
||||||
|
}
|
||||||
|
if (ysmatch)
|
||||||
|
goto match;
|
||||||
|
}
|
||||||
|
} /* Y_CHOICE */
|
||||||
|
else
|
||||||
|
if (yang_schemanode(ys)){
|
||||||
|
if (argument == NULL)
|
||||||
|
ysmatch = ys;
|
||||||
|
else
|
||||||
|
if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0)
|
||||||
|
ysmatch = ys;
|
||||||
|
if (ysmatch)
|
||||||
|
goto match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match:
|
||||||
|
return ysmatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*! Find first matching data node in all (sub)modules in a yang spec
|
||||||
*
|
*
|
||||||
* @param[in] ysp Yang specification
|
* @param[in] ysp Yang specification
|
||||||
* @param[in] name if NULL, match any(first) argument. XXX is that really a case?
|
* @param[in] name if NULL, match any(first) argument. XXX is that really a case?
|
||||||
|
* @param[in] schemanode If set look for schema nodes, otherwise only data nodes
|
||||||
* A yang specification has modules as children which in turn can have
|
* A yang specification has modules as children which in turn can have
|
||||||
* syntax-nodes as children. This function goes through all the modules to
|
* syntax-nodes as children. This function goes through all the modules to
|
||||||
* look for syntax-nodes. Note that if a child to a module is a choice,
|
* look for nodes. Note that if a child to a module is a choice,
|
||||||
* the search is made recursively made to the choice's children.
|
* the search is made recursively made to the choice's children.
|
||||||
*/
|
*/
|
||||||
yang_stmt *
|
yang_stmt *
|
||||||
yang_find_topnode(yang_spec *ysp,
|
yang_find_topnode(yang_spec *ysp,
|
||||||
char *name)
|
char *name,
|
||||||
|
int schemanode)
|
||||||
{
|
{
|
||||||
yang_stmt *ys = NULL;
|
yang_stmt *ys = NULL;
|
||||||
yang_stmt *yc = NULL;
|
yang_stmt *yc = NULL;
|
||||||
|
|
@ -472,12 +522,43 @@ yang_find_topnode(yang_spec *ysp,
|
||||||
|
|
||||||
for (i=0; i<ysp->yp_len; i++){
|
for (i=0; i<ysp->yp_len; i++){
|
||||||
ys = ysp->yp_stmt[i];
|
ys = ysp->yp_stmt[i];
|
||||||
if ((yc = yang_find_datanode((yang_node*)ys, name)) != NULL)
|
if (schemanode){
|
||||||
return yc;
|
if ((yc = yang_find_schemanode((yang_node*)ys, name)) != NULL)
|
||||||
|
return yc;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if ((yc = yang_find_datanode((yang_node*)ys, name)) != NULL)
|
||||||
|
return yc;
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! Given a yang statement, find the prefix associated to this module
|
||||||
|
* @param[in] ys Yang statement
|
||||||
|
* @retval NULL Not found
|
||||||
|
* @retval prefix Prefix as char* pointer into yang tree
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
yang_find_myprefix(yang_stmt *ys)
|
||||||
|
{
|
||||||
|
yang_stmt *ymod; /* My module */
|
||||||
|
yang_stmt *yprefix;
|
||||||
|
char *prefix = NULL;
|
||||||
|
|
||||||
|
if ((ymod = ys_module(ys)) == NULL){
|
||||||
|
clicon_err(OE_YANG, 0, "My yang module not found");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if ((yprefix = yang_find((yang_node*)ymod, Y_PREFIX, NULL)) == NULL){
|
||||||
|
clicon_err(OE_YANG, 0, "No prefix in my module");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
prefix = yprefix->ys_argument;
|
||||||
|
done:
|
||||||
|
return prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*! Reset flag in complete tree, arg contains flag */
|
/*! Reset flag in complete tree, arg contains flag */
|
||||||
static int
|
static int
|
||||||
ys_flag_reset(yang_stmt *ys,
|
ys_flag_reset(yang_stmt *ys,
|
||||||
|
|
@ -505,8 +586,13 @@ ys_module(yang_stmt *ys)
|
||||||
{
|
{
|
||||||
yang_node *yn;
|
yang_node *yn;
|
||||||
|
|
||||||
|
#if 1
|
||||||
|
if (ys==NULL || ys->ys_keyword==Y_SPEC)
|
||||||
|
return NULL;
|
||||||
|
#else
|
||||||
if (ys==NULL || ys->ys_keyword==Y_SPEC)
|
if (ys==NULL || ys->ys_keyword==Y_SPEC)
|
||||||
return ys;
|
return ys;
|
||||||
|
#endif
|
||||||
while (ys != NULL && ys->ys_keyword != Y_MODULE && ys->ys_keyword != Y_SUBMODULE){
|
while (ys != NULL && ys->ys_keyword != Y_MODULE && ys->ys_keyword != Y_SUBMODULE){
|
||||||
yn = ys->ys_parent;
|
yn = ys->ys_parent;
|
||||||
/* Some extra stuff to ensure ys is a stmt */
|
/* Some extra stuff to ensure ys is a stmt */
|
||||||
|
|
@ -572,6 +658,7 @@ ytype_prefix(yang_stmt *ys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*! Given a yang statement and a prefix, return yang module to that prefix
|
/*! Given a yang statement and a prefix, return yang module to that prefix
|
||||||
* Note, not the other module but the proxy import statement only
|
* Note, not the other module but the proxy import statement only
|
||||||
* @param[in] ys A yang statement
|
* @param[in] ys A yang statement
|
||||||
|
|
@ -588,22 +675,25 @@ yang_find_module_by_prefix(yang_stmt *ys,
|
||||||
yang_stmt *my_ymod;
|
yang_stmt *my_ymod;
|
||||||
yang_stmt *ymod = NULL;
|
yang_stmt *ymod = NULL;
|
||||||
yang_spec *yspec;
|
yang_spec *yspec;
|
||||||
|
char *myprefix;
|
||||||
|
|
||||||
|
if ((yspec = ys_spec(ys)) == NULL){
|
||||||
|
clicon_err(OE_YANG, 0, "My yang spec not found");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
myprefix = yang_find_myprefix(ys);
|
||||||
if ((my_ymod = ys_module(ys)) == NULL){
|
if ((my_ymod = ys_module(ys)) == NULL){
|
||||||
clicon_err(OE_YANG, 0, "My yang module not found");
|
clicon_err(OE_YANG, 0, "My yang module not found");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if ((yspec = ys_spec(my_ymod)) == NULL){
|
#if 0
|
||||||
clicon_err(OE_YANG, 0, "My yang spec not found");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if (my_ymod->ys_keyword != Y_MODULE &&
|
if (my_ymod->ys_keyword != Y_MODULE &&
|
||||||
my_ymod->ys_keyword != Y_SUBMODULE){
|
my_ymod->ys_keyword != Y_SUBMODULE){
|
||||||
clicon_err(OE_YANG, 0, "%s not module or sub-module", my_ymod->ys_argument);
|
clicon_err(OE_YANG, 0, "%s not module or sub-module", my_ymod->ys_argument);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if ((yprefix = yang_find((yang_node*)my_ymod, Y_PREFIX, NULL)) != NULL &&
|
#endif
|
||||||
strcmp(yprefix->ys_argument, prefix) == 0){
|
if (strcmp(myprefix, prefix) == 0){
|
||||||
ymod = my_ymod;
|
ymod = my_ymod;
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
@ -1730,6 +1820,7 @@ yang_abs_schema_nodeid(yang_spec *yspec,
|
||||||
yang_stmt *ymod;
|
yang_stmt *ymod;
|
||||||
char *id;
|
char *id;
|
||||||
char *prefix = NULL;
|
char *prefix = NULL;
|
||||||
|
|
||||||
yang_stmt *yprefix;
|
yang_stmt *yprefix;
|
||||||
|
|
||||||
/* check absolute schema_nodeid */
|
/* check absolute schema_nodeid */
|
||||||
|
|
@ -1765,9 +1856,16 @@ yang_abs_schema_nodeid(yang_spec *yspec,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ymod == NULL){
|
if (ymod == NULL){ /* Try with topnode */
|
||||||
clicon_err(OE_YANG, 0, "Module with prefix %s not found", prefix);
|
yang_stmt *ys;
|
||||||
goto done;
|
if ((ys = yang_find_topnode(yspec, id, 1)) == NULL){
|
||||||
|
clicon_err(OE_YANG, 0, "Module with id:%s:%s not found", prefix,id);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if ((ymod = ys_module(ys)) == NULL){
|
||||||
|
clicon_err(OE_YANG, 0, "Module with id:%s:%s not found2", prefix,id);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (schema_nodeid_vec((yang_node*)ymod, vec+1, nvec-1, yres) < 0)
|
if (schema_nodeid_vec((yang_node*)ymod, vec+1, nvec-1, yres) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,8 @@ yang_builtin(char *type)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! Set type cache for yang type
|
||||||
|
*/
|
||||||
int
|
int
|
||||||
yang_type_cache_set(yang_type_cache **ycache0,
|
yang_type_cache_set(yang_type_cache **ycache0,
|
||||||
yang_stmt *resolved,
|
yang_stmt *resolved,
|
||||||
|
|
@ -142,7 +144,6 @@ yang_type_cache_set(yang_type_cache **ycache0,
|
||||||
}
|
}
|
||||||
ycache->yc_fraction = fraction;
|
ycache->yc_fraction = fraction;
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
||||||
done:
|
done:
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
@ -209,6 +210,7 @@ yang_type_cache_free(yang_type_cache *ycache)
|
||||||
* @param[in] ys This is a type statement
|
* @param[in] ys This is a type statement
|
||||||
* @param[in] arg Not used
|
* @param[in] arg Not used
|
||||||
* Typically only called once when loading te yang type system.
|
* Typically only called once when loading te yang type system.
|
||||||
|
* @note unions not cached
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
ys_resolve_type(yang_stmt *ys,
|
ys_resolve_type(yang_stmt *ys,
|
||||||
|
|
@ -226,13 +228,16 @@ ys_resolve_type(yang_stmt *ys,
|
||||||
if (yang_type_resolve((yang_stmt*)ys->ys_parent, ys, &resolved,
|
if (yang_type_resolve((yang_stmt*)ys->ys_parent, ys, &resolved,
|
||||||
&options, &mincv, &maxcv, &pattern, &fraction) < 0)
|
&options, &mincv, &maxcv, &pattern, &fraction) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
/* skip unions since they may have different sets of options, mincv, etc */
|
|
||||||
if (resolved && strcmp(resolved->ys_argument, "union")==0)
|
if (resolved && strcmp(resolved->ys_argument, "union")==0)
|
||||||
;
|
;
|
||||||
|
/* skip unions since they may have different sets of options, mincv, etc
|
||||||
|
* You would have to resolve all sub-types also recursively
|
||||||
|
*/
|
||||||
else
|
else
|
||||||
if (yang_type_cache_set(&ys->ys_typecache,
|
if (yang_type_cache_set(&ys->ys_typecache,
|
||||||
resolved, options, mincv, maxcv, pattern, fraction) < 0)
|
resolved, options, mincv, maxcv, pattern, fraction) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
return retval;
|
return retval;
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,9 @@ new "netconf check empty startup"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><get-config><source><startup/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data/></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><get-config><source><startup/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "netconf rpc"
|
new "netconf rpc"
|
||||||
|
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><rt:fib-route><routing-instance-name>ipv4</routing-instance-name><destination-address><address-family>ipv4</address-family></destination-address></rt:fib-route></rpc>]]>]]>" "^<rpc-reply><route><address-family>ipv4</address-family><next-hop><next-hop-list>"
|
||||||
|
|
||||||
|
new "netconf rpc w/o namespace"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><fib-route><routing-instance-name>ipv4</routing-instance-name><destination-address><address-family>ipv4</address-family></destination-address></fib-route></rpc>]]>]]>" "^<rpc-reply><route><address-family>ipv4</address-family><next-hop><next-hop-list>"
|
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><fib-route><routing-instance-name>ipv4</routing-instance-name><destination-address><address-family>ipv4</address-family></destination-address></fib-route></rpc>]]>]]>" "^<rpc-reply><route><address-family>ipv4</address-family><next-hop><next-hop-list>"
|
||||||
|
|
||||||
new "netconf subscription"
|
new "netconf subscription"
|
||||||
|
|
|
||||||
|
|
@ -35,53 +35,55 @@ expectfn "curl -sS -I http://localhost/restconf/data" "Content-Type: application
|
||||||
new "restconf get empty config"
|
new "restconf get empty config"
|
||||||
expectfn "curl -sSG http://localhost/restconf/data" "^null
$"
|
expectfn "curl -sSG http://localhost/restconf/data" "^null
$"
|
||||||
|
|
||||||
#
|
new "restconf Add subtree to datastore using POST"
|
||||||
new "Add subtree to datastore using POST"
|
|
||||||
expectfn 'curl -sS -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}}} http://localhost/restconf/data' ""
|
expectfn 'curl -sS -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}}} http://localhost/restconf/data' ""
|
||||||
|
|
||||||
new "Check interfaces eth/0/0 added"
|
new "restconf Check interfaces eth/0/0 added"
|
||||||
expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}}
|
expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}}
|
||||||
$'
|
$'
|
||||||
|
|
||||||
new "delete interfaces"
|
new "restconf delete interfaces"
|
||||||
expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces' ""
|
expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces' ""
|
||||||
|
|
||||||
new "Check empty config"
|
new "restconf Check empty config"
|
||||||
expectfn "curl -sSG http://localhost/restconf/data" "^null
$"
|
expectfn "curl -sSG http://localhost/restconf/data" "^null
$"
|
||||||
|
|
||||||
new "Add interfaces subtree eth/0/0 using POST"
|
new "restconf Add interfaces subtree eth/0/0 using POST"
|
||||||
expectfn 'curl -sS -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces' ""
|
expectfn 'curl -sS -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces' ""
|
||||||
|
|
||||||
new "Check eth/0/0 added"
|
new "restconf Check eth/0/0 added"
|
||||||
expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}}
|
expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}}
|
||||||
$'
|
$'
|
||||||
|
|
||||||
new "Re-post eth/0/0 which should generate error"
|
new "restconf Re-post eth/0/0 which should generate error"
|
||||||
expectfn 'curl -sS -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces' "Data resource already exists"
|
expectfn 'curl -sS -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces' "Data resource already exists"
|
||||||
|
|
||||||
new "Add leaf description using POST"
|
new "Add leaf description using POST"
|
||||||
expectfn 'curl -sS -X POST -d {"description":"The-first-interface"} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' ""
|
expectfn 'curl -sS -X POST -d {"description":"The-first-interface"} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' ""
|
||||||
|
|
||||||
new "Check description added"
|
new "restconf Check description added"
|
||||||
expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","description": "The-first-interface","type": "eth","enabled": "true"}}
|
expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","description": "The-first-interface","type": "eth","enabled": "true"}}
|
||||||
$'
|
$'
|
||||||
|
|
||||||
new "delete eth/0/0"
|
new "restconf delete eth/0/0"
|
||||||
expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' ""
|
expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' ""
|
||||||
|
|
||||||
#new "Check deleted eth/0/0"
|
new "Check deleted eth/0/0"
|
||||||
#expectfn 'curl -sS -G http://localhost/restconf/data' '{"interfaces": null}
|
expectfn 'curl -sS -G http://localhost/restconf/data' "^null
$"
|
||||||
#$'
|
|
||||||
|
|
||||||
new "Re-Delete eth/0/0 using none should generate error"
|
new "restconf Re-Delete eth/0/0 using none should generate error"
|
||||||
expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "Not Found"
|
expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "Not Found"
|
||||||
|
|
||||||
new "Add subtree eth/0/0 using PUT"
|
new "restconf Add subtree eth/0/0 using PUT"
|
||||||
expectfn 'curl -sS -X PUT -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' ""
|
expectfn 'curl -sS -X PUT -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' ""
|
||||||
|
|
||||||
|
new "restconf get subtree"
|
||||||
expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}}
|
expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}}
|
||||||
$'
|
$'
|
||||||
|
|
||||||
|
new "restconf rpc using POST (wrong output encoding)"
|
||||||
|
expectfn 'curl -sS -X POST -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/rt:fib-route' '{"rpc-reply": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"}}}}
$'
|
||||||
|
|
||||||
new "Kill restconf daemon"
|
new "Kill restconf daemon"
|
||||||
sudo pkill -u www-data clixon_restconf
|
sudo pkill -u www-data clixon_restconf
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue