Merge branch 'develop'

This commit is contained in:
Olof hagsand 2017-08-01 20:01:16 +02:00
commit a8ea3ead11
24 changed files with 850 additions and 330 deletions

View file

@ -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,10 +52,9 @@ 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:
@ -66,6 +67,8 @@ 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:
@ -86,6 +89,7 @@ If you submit "nopresence" without a leaf, it will automatically be removed:
* You need to define state data in a backend callback. See the example and documentation for more details. * You need to define state data in a backend callback. See the example and documentation for more details.
### Minor changes: ### Minor changes:
* Added xpath support for predicate: current(), eg /interface[name=current()/../name]
* Added prefix parsing of xpath, allowing eg /p:x/p:y, but prefix ignored. * Added prefix parsing of xpath, allowing eg /p:x/p:y, but prefix ignored.
* Corrected Yang union CLI generation and type validation. Recursive unions did not work. * Corrected Yang union CLI generation and type validation. Recursive unions did not work.
* Corrected Yang pattern type escaping problem, ie '\.' did not work properly. This requires update of cligen as well. * Corrected Yang pattern type escaping problem, ie '\.' did not work properly. This requires update of cligen as well.

View file

@ -88,9 +88,14 @@ used to generate an interactive CLI client as well as provide
[Restconf](apps/restconf/README.md) clients. [Restconf](apps/restconf/README.md) clients.
The [YANG RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) is implemented with the following exceptions: The [YANG RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) is implemented with the following exceptions:
- object-references - conformance: feature, if-feature, deviation
- if-feature - identy, base, identityref
- unique - list features: min/max-elements, unique, ordered-by
There are also new features in YANG 1.1 [YANG RFC
7950](https://www.rfc-editor.org/rfc/rfc7950.txt) most of which are
not implemented.

View file

@ -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;

View file

@ -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

View file

@ -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)
@ -287,7 +269,7 @@ netconf_plugin_callbacks(clicon_handle h,
if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) != NULL){ if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) != NULL){
xoutput=xpath_first(*xret, "/"); xoutput=xpath_first(*xret, "/");
xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */
if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yinput) < 0) if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, youtput) < 0)
goto done; goto done;
if (xml_apply(xoutput, CX_ELMNT, if (xml_apply(xoutput, CX_ELMNT,
(xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) (xml_applyfn_t*)xml_yang_validate_all, NULL) < 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;
} }

View file

@ -264,7 +264,19 @@ test(FCGX_Request *r,
printparam(r, "SERVER_NAME", dbg); printparam(r, "SERVER_NAME", dbg);
printparam(r, "HTTP_COOKIE", dbg); printparam(r, "HTTP_COOKIE", dbg);
printparam(r, "HTTPS", dbg); printparam(r, "HTTPS", dbg);
printparam(r, "HTTP_ACCEPT", dbg);
printparam(r, "HTTP_CONTENT_TYPE", dbg);
#if 0 /* For debug */
clicon_debug(1, "All environment vars:");
{
extern char **environ;
int i;
for (i = 0; environ[i] != NULL; i++){
clicon_debug(1, "%s", environ[i]);
}
}
clicon_debug(1, "End environment vars:");
#endif
return 0; return 0;
} }

View file

@ -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,37 @@ 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
*/ */
@ -157,12 +188,10 @@ request_process(clicon_handle h,
clicon_debug(1, "DATA=%s", data); clicon_debug(1, "DATA=%s", data);
if (str2cvec(data, '&', '=', &dvec) < 0) if (str2cvec(data, '&', '=', &dvec) < 0)
goto done; goto done;
if ((method = pvec[2]) == NULL){ if ((method = pvec[2]) == NULL){
retval = notfound(r); retval = notfound(r);
goto done; goto done;
} }
retval = 0; retval = 0;
test(r, 1); test(r, 1);
/* If present, check credentials */ /* If present, check credentials */
@ -176,6 +205,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
@ -357,7 +389,7 @@ main(int argc,
if ((path = FCGX_GetParam("REQUEST_URI", r->envp)) != NULL){ if ((path = FCGX_GetParam("REQUEST_URI", r->envp)) != NULL){
if (strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)) == 0 || if (strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)) == 0 ||
strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)-1) == 0) strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)-1) == 0)
request_process(h, r); request_process(h, r); /* This is the function */
else{ else{
clicon_debug(1, "top-level not found"); clicon_debug(1, "top-level not found");
notfound(r); notfound(r);

View file

@ -161,8 +161,13 @@ api_data_get_gen(clicon_handle h,
cbuf *cbj = NULL;; cbuf *cbj = NULL;;
int code; int code;
const char *reason_phrase; const char *reason_phrase;
char *media_accept;
int use_xml = 0; /* By default use JSON */
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp);
if (strcmp(media_accept, "application/yang-data+xml")==0)
use_xml++;
yspec = clicon_dbspec_yang(h); yspec = clicon_dbspec_yang(h);
if ((path = cbuf_new()) == NULL) if ((path = cbuf_new()) == NULL)
goto done; goto done;
@ -213,15 +218,22 @@ api_data_get_gen(clicon_handle h,
if ((cbx = cbuf_new()) == NULL) if ((cbx = cbuf_new()) == NULL)
goto done; goto done;
FCGX_SetExitStatus(200, r->out); /* OK */ FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: application/yang.data+json\r\n"); FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
FCGX_FPrintF(r->out, "\r\n"); FCGX_FPrintF(r->out, "\r\n");
if (head) if (head)
goto ok; goto ok;
clicon_debug(1, "%s name:%s child:%d", __FUNCTION__, xml_name(xret), xml_child_nr(xret)); clicon_debug(1, "%s name:%s child:%d", __FUNCTION__, xml_name(xret), xml_child_nr(xret));
vec = xml_childvec_get(xret);
clicon_debug(1, "%s xretnr:%d", __FUNCTION__, xml_child_nr(xret)); clicon_debug(1, "%s xretnr:%d", __FUNCTION__, xml_child_nr(xret));
if (use_xml){
if (clicon_xml2cbuf(cbx, xret, 0, 1) < 0) /* Dont print top object? */
goto done;
}
else{
vec = xml_childvec_get(xret);
if (xml2json_cbuf_vec(cbx, vec, xml_child_nr(xret), 0) < 0) if (xml2json_cbuf_vec(cbx, vec, xml_child_nr(xret), 0) < 0)
goto done; goto done;
}
clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx)); clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx));
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
FCGX_FPrintF(r->out, "\r\n\r\n"); FCGX_FPrintF(r->out, "\r\n\r\n");
@ -276,8 +288,8 @@ api_data_head(clicon_handle h,
* Request may contain * Request may contain
* Accept: application/yang.data+json,application/yang.data+xml * Accept: application/yang.data+json,application/yang.data+xml
* Response contains one of: * Response contains one of:
* Content-Type: application/yang.data+xml * Content-Type: application/yang-data+xml
* Content-Type: application/yang.data+json * Content-Type: application/yang-data+json
* NOTE: If a retrieval request for a data resource representing a YANG leaf- * NOTE: If a retrieval request for a data resource representing a YANG leaf-
* list or list object identifies more than one instance, and XML * list or list object identifies more than one instance, and XML
* encoding is used in the response, then an error response containing a * encoding is used in the response, then an error response containing a
@ -340,10 +352,15 @@ api_data_post(clicon_handle h,
yang_node *y = NULL; yang_node *y = NULL;
yang_spec *yspec; yang_spec *yspec;
cxobj *xa; cxobj *xa;
char *media_content_type;
int parse_xml = 0; /* By default expect and parse JSON */
clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"",
__FUNCTION__, __FUNCTION__,
api_path, data); api_path, data);
media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp);
if (strcmp(media_content_type, "application/yang-data+xml")==0)
parse_xml++;
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;
@ -354,10 +371,16 @@ 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 or xml into xml */
if (json_parse_str(data, &xdata) < 0){ if (parse_xml){
if (clicon_xml_parse_str(data, &xdata) < 0){
clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data);
goto done;
}
}
else if (json_parse_str(data, &xdata) < 0){
clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data); clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data);
goto done; goto done;
} }
@ -448,10 +471,15 @@ api_data_put(clicon_handle h,
yang_node *y = NULL; yang_node *y = NULL;
yang_spec *yspec; yang_spec *yspec;
cxobj *xa; cxobj *xa;
char *media_content_type;
int parse_xml = 0; /* By default expect and parse JSON */
clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"",
__FUNCTION__, __FUNCTION__,
api_path, data); api_path, data);
media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp);
if (strcmp(media_content_type, "application/yang-data+xml")==0)
parse_xml++;
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;
@ -462,10 +490,16 @@ 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 or xml into xml */
if (json_parse_str(data, &xdata) < 0){ if (parse_xml){
if (clicon_xml_parse_str(data, &xdata) < 0){
clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data);
goto done;
}
}
else if (json_parse_str(data, &xdata) < 0){
clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data); clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data);
goto done; goto done;
} }
@ -581,7 +615,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;
@ -614,3 +648,158 @@ 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;
yang_stmt *youtput;
cxobj *xdata = NULL;
cxobj *xret = NULL;
cbuf *cbx = NULL;
cxobj *xtop = NULL; /* xpath root */
cxobj *xbot = NULL;
yang_node *y = NULL;
cxobj *xinput;
cxobj *xoutput;
cxobj *x;
char *media_content_type;
int parse_xml = 0; /* By default expect and parse JSON */
char *media_accept;
int use_xml = 0; /* By default return JSON */
clicon_debug(1, "%s json:\"%s\"", __FUNCTION__, data);
media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp);
if (strcmp(media_accept, "application/yang-data+xml")==0)
use_xml++;
media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp);
if (strcmp(media_content_type, "application/yang-data+xml")==0)
parse_xml++;
clicon_debug(1, "%s accept:\"%s\" content-type:\"%s\"",
__FUNCTION__, media_accept, media_content_type);
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 or xml into xml */
if (parse_xml){
if (clicon_xml_parse_str(data, &xdata) < 0){
clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data);
goto done;
}
}
else 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;
xoutput=xpath_first(xret, "/");
if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) != NULL &&
xoutput){
xml_name_set(xoutput, "output");
clicon_debug(1, "%s xoutput:%s", __FUNCTION__, cbuf_get(cbx));
cbuf_reset(cbx);
xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */
if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, youtput) < 0)
goto done;
if (xml_apply(xoutput, CX_ELMNT,
(xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
goto done;
if (xml_yang_validate_add(xoutput, NULL) < 0)
goto done;
}
/* Sanity check of outgoing XML */
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
FCGX_FPrintF(r->out, "\r\n");
if (xoutput){
if (use_xml){
if (clicon_xml2cbuf(cbx, xoutput, 0, 1) < 0)
goto done;
}
else
if (xml2json_cbuf(cbx, xoutput, 1) < 0)
goto done;
clicon_debug(1, "%s xoutput:%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 (xtop)
xml_free(xtop);
if (xret)
xml_free(xret);
if (cbx)
cbuf_free(cbx);
return retval;
}

View file

@ -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_ */

View file

@ -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;
} }

View file

@ -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;
} }

View file

@ -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,9 @@ 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);
#endif /* _CLIXON_XML_MAP_H_ */ #endif /* _CLIXON_XML_MAP_H_ */

View file

@ -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);
@ -213,9 +215,10 @@ int yang_parse(clicon_handle h, const char *yang_dir,
const char *module, const char *revision, yang_spec *ysp); const char *module, const char *revision, yang_spec *ysp);
int yang_apply(yang_node *yn, enum rfc_6020 key, yang_applyfn_t fn, int yang_apply(yang_node *yn, enum rfc_6020 key, yang_applyfn_t fn,
void *arg); void *arg);
yang_node *yang_abs_schema_nodeid(yang_node *yn, char *schema_nodeid); int yang_abs_schema_nodeid(yang_spec *yspec, char *schema_nodeid,
yang_node *yang_desc_schema_nodeid(yang_node *yn, char *schema_nodeid); yang_stmt **yres);
yang_node *yang_schema_nodeid(yang_node *yn, char *xpath); int yang_desc_schema_nodeid(yang_node *yn, char *schema_nodeid,
yang_stmt **yres);
cg_var *ys_parse(yang_stmt *ys, enum cv_type cvtype); cg_var *ys_parse(yang_stmt *ys, enum cv_type cvtype);
int ys_parse_sub(yang_stmt *ys); int ys_parse_sub(yang_stmt *ys);
int yang_mandatory(yang_stmt *ys); int yang_mandatory(yang_stmt *ys);

View file

@ -412,7 +412,7 @@ xml2json1_cbuf(cbuf *cb,
* goto err; * goto err;
* cbuf_free(cb); * cbuf_free(cb);
* @endcode * @endcode
* See also xml2json * @see clicon_xml2cbuf
*/ */
int int
xml2json_cbuf(cbuf *cb, xml2json_cbuf(cbuf *cb,
@ -442,7 +442,7 @@ xml2json_cbuf(cbuf *cb,
/*! Translate a vector of xml objects to JSON CLigen buffer. /*! Translate a vector of xml objects to JSON CLigen buffer.
* This is done by adding a top pseudo-object, and add the vector as subs, * This is done by adding a top pseudo-object, and add the vector as subs,
* and then not pritning the top pseudo-.object using the 'flat' option. * and then not printing the top pseudo-object using the 'flat' option.
* @param[out] cb Cligen buffer to write to * @param[out] cb Cligen buffer to write to
* @param[in] vec Vector of xml objecst * @param[in] vec Vector of xml objecst
* @param[in] veclen Length of vector * @param[in] veclen Length of vector

View file

@ -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");

View file

@ -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,6 +1635,7 @@ 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
@ -1644,6 +1645,7 @@ api_path2xml_vec(char **vec,
int nvec, int nvec,
cxobj *x0, cxobj *x0,
yang_node *y0, yang_node *y0,
int schemanode,
cxobj **xpathp, cxobj **xpathp,
yang_node **ypathp) yang_node **ypathp)
{ {
@ -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,6 +1784,8 @@ 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
@ -1777,6 +1794,7 @@ 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;
} }
@ -1999,3 +2017,55 @@ xml_merge(cxobj *x0,
return retval; return retval;
} }
/*! Get integer value from xml node from yang enumeration
* @param[in] node XML node in a tree
* @param[out] val Integer value returned
* @retval 0 OK, value parsed
* @retval -1 Error, value not obtained or parsed, no reason given
* @note assume yang spec set
* @note The function only returns assigned values, but according to RFC:
If a value is not specified, then one will be automatically assigned.
If the "enum" substatement is the first one defined, the assigned
value is zero (0); otherwise, the assigned value is one greater than
the current highest enum value (i.e., the highest enum value,
* Thanks: Matthew Smith
*/
int
yang_enum_int_value(cxobj *node,
int32_t *val)
{
int retval = -1;
yang_spec *yspec;
yang_stmt *ys;
yang_stmt *ytype;
yang_stmt *yrestype; /* resolved type */
yang_stmt *yenum;
yang_stmt *yval;
char *reason = NULL;
if (node == NULL)
goto done;
if ((ys = (yang_stmt *) xml_spec(node)) == NULL)
goto done;
if ((yspec = ys_spec(ys)) == NULL)
goto done;
if ((ytype = yang_find((yang_node *)ys, Y_TYPE, NULL)) == NULL)
goto done;
if (yang_type_resolve(ys, ytype, &yrestype,
NULL, NULL, NULL, NULL, NULL) < 0)
goto done;
if (yrestype==NULL || strcmp(yrestype->ys_argument, "enumeration"))
goto done;
if ((yenum = yang_find((yang_node *)yrestype, Y_ENUM, xml_body(node))) == NULL)
goto done;
/* Should assign value if yval not found */
if ((yval = yang_find((yang_node *)yenum, Y_VALUE, NULL)) == NULL)
goto done;
/* reason is string containing why int could not be parsed */
if (parse_int32(yval->ys_argument, val, &reason) < 0)
goto done;
retval = 0;
done:
return retval;
}

View file

@ -37,7 +37,7 @@
* Look at the end of the file for a test unit program * Look at the end of the file for a test unit program
*/ */
/* /*
https://www.w3.org/TR/xpath/ See https://www.w3.org/TR/xpath/
Implementation of a limited xslt xpath syntax. Some examples. Given the following Implementation of a limited xslt xpath syntax. Some examples. Given the following
xml tree: xml tree:
@ -172,6 +172,9 @@ xpath_print(FILE *f, struct xpath_element *xplist)
return 0; return 0;
} }
/*! Extract PredicateExpr (Expr) from a Predicate within []
* @see xpath_expr For evaluation of predicate
*/
static int static int
xpath_parse_predicate(struct xpath_element *xe, xpath_parse_predicate(struct xpath_element *xe,
char *pred) char *pred)
@ -439,7 +442,13 @@ recursive_find(cxobj *xn,
return retval; return retval;
} }
/* forward */
static int
xpath_exec(cxobj *xcur, char *xpath, cxobj **vec0, size_t vec0len,
uint16_t flags, cxobj ***vec2, size_t *vec2len);
/*! XPath predicate expression check /*! XPath predicate expression check
* @param[in] xcur xml-tree where to search
* @param[in] predicate_expression xpath expression as a string * @param[in] predicate_expression xpath expression as a string
* @param[in] flags Extra xml flag checks that must match (apart from predicate) * @param[in] flags Extra xml flag checks that must match (apart from predicate)
* @param[in,out] vec0 Vector or xml nodes that are checked. Not matched are filtered * @param[in,out] vec0 Vector or xml nodes that are checked. Not matched are filtered
@ -454,7 +463,8 @@ recursive_find(cxobj *xn,
* @see https://www.w3.org/TR/xpath/#predicates * @see https://www.w3.org/TR/xpath/#predicates
*/ */
static int static int
xpath_expr(char *predicate_expression, xpath_expr(cxobj *xcur,
char *predicate_expression,
uint16_t flags, uint16_t flags,
cxobj ***vec0, cxobj ***vec0,
size_t *vec0len) size_t *vec0len)
@ -462,6 +472,7 @@ xpath_expr(char *predicate_expression,
char *e_a; char *e_a;
char *e_v; char *e_v;
int i; int i;
int j;
int retval = -1; int retval = -1;
cxobj *x; cxobj *x;
cxobj *xv; cxobj *xv;
@ -520,12 +531,58 @@ xpath_expr(char *predicate_expression,
goto done; goto done;
} }
} }
else{ else{ /* name = expr */
if ((tag = strsep(&e, "=")) == NULL){ if ((tag = strsep(&e, "=")) == NULL){
clicon_err(OE_XML, errno, "%s: malformed expression: [%s]", clicon_err(OE_XML, errno, "%s: malformed expression: [%s]",
__FUNCTION__, e); __FUNCTION__, e);
goto done; goto done;
} }
/* Strip trailing spaces */
while (tag[strlen(tag)-1] == ' ')
tag[strlen(tag)-1] = '\0';
/* Strip heading spaces */
while (e[0]==' ')
e++;
if (strncmp(e, "current()", strlen("current()")) == 0){
/* name = current()xpath */
cxobj **svec0 = NULL;
size_t svec0len = 0;
cxobj **svec1 = NULL;
size_t svec1len = 0;
char *ebody;
e += strlen("current("); /* e is path expression */
*e = '.';
if ((svec0 = calloc(1, sizeof(cxobj *))) == NULL){
clicon_err(OE_UNIX, errno, "calloc");
goto done;
}
svec0[0] = xcur;
svec0len++;
/* Recursive invocation */
if (xpath_exec(xcur, e, svec0, svec0len,
flags, &svec1, &svec1len) < 0)
goto done;
for (j=0; j<svec1len; j++){
ebody = xml_body(svec1[j]);
for (i=0; i<*vec0len; i++){
xv = (*vec0)[i];
x = NULL;
while ((x = xml_child_each(xv, x, CX_ELMNT)) != NULL) {
if (strcmp(tag, xml_name(x)) != 0)
continue;
if ((val = xml_body(x)) != NULL &&
strcmp(val, ebody) == 0){
clicon_debug(2, "%s %x %x", __FUNCTION__, flags, xml_flag(xv, flags));
if (flags==0x0 || xml_flag(xv, flags))
if (cxvec_append(xv, &vec, &veclen) < 0)
goto done;
}
}
}
}
}
else { /* name = value */
for (i=0; i<*vec0len; i++){ for (i=0; i<*vec0len; i++){
xv = (*vec0)[i]; xv = (*vec0)[i];
/* Check if more may match,... */ /* Check if more may match,... */
@ -544,6 +601,7 @@ xpath_expr(char *predicate_expression,
} }
} }
} }
}
/* copy the array from 1 to 0 */ /* copy the array from 1 to 0 */
free(*vec0); free(*vec0);
*vec0 = vec; *vec0 = vec;
@ -556,6 +614,7 @@ xpath_expr(char *predicate_expression,
} }
/*! Given vec0, add matches to vec1 /*! Given vec0, add matches to vec1
* @param[in] xcur xml-tree where to search
* @param[in] xe XPATH in structured (parsed) form * @param[in] xe XPATH in structured (parsed) form
* @param[in] descendants0 * @param[in] descendants0
* @param[in] vec0 vector of XML trees * @param[in] vec0 vector of XML trees
@ -565,7 +624,8 @@ xpath_expr(char *predicate_expression,
* @param[out] vec2len Length of result vector. * @param[out] vec2len Length of result vector.
*/ */
static int static int
xpath_find(struct xpath_element *xe, xpath_find(cxobj *xcur,
struct xpath_element *xe,
int descendants0, int descendants0,
cxobj **vec0, cxobj **vec0,
size_t vec0len, size_t vec0len,
@ -669,10 +729,10 @@ xpath_find(struct xpath_element *xe,
} }
for (xp = xe->xe_predicate; xp; xp = xp->xp_next){ for (xp = xe->xe_predicate; xp; xp = xp->xp_next){
if (xpath_expr(xp->xp_expr, flags, &vec0, &vec0len) < 0) if (xpath_expr(xcur, xp->xp_expr, flags, &vec0, &vec0len) < 0)
goto done; goto done;
} }
if (xpath_find(xe->xe_next, descendants, if (xpath_find(xcur, xe->xe_next, descendants,
vec0, vec0len, flags, vec0, vec0len, flags,
vec2, vec2len) < 0) vec2, vec2len) < 0)
goto done; goto done;
@ -719,6 +779,7 @@ xpath_split(char *xpathstr,
} }
/*! Process single xpath expression on xml tree /*! Process single xpath expression on xml tree
* @param[in] xcur xml-tree where to search
* @param[in] xpath string with XPATH syntax * @param[in] xpath string with XPATH syntax
* @param[in] vec0 vector of XML trees * @param[in] vec0 vector of XML trees
* @param[in] vec0len length of XML trees * @param[in] vec0len length of XML trees
@ -727,7 +788,8 @@ xpath_split(char *xpathstr,
* @param[out] vec2len Length of result vector. * @param[out] vec2len Length of result vector.
*/ */
static int static int
xpath_exec(char *xpath, xpath_exec(cxobj *xcur,
char *xpath,
cxobj **vec0, cxobj **vec0,
size_t vec0len, size_t vec0len,
uint16_t flags, uint16_t flags,
@ -744,7 +806,7 @@ xpath_exec(char *xpath,
goto done; goto done;
if (debug > 1) if (debug > 1)
xpath_print(stderr, xplist); xpath_print(stderr, xplist);
if (xpath_find(xplist, 0, vec1, vec1len, flags, vec2, vec2len) < 0) if (xpath_find(xcur, xplist, 0, vec1, vec1len, flags, vec2, vec2len) < 0)
goto done; goto done;
if (xpath_free(xplist) < 0) if (xpath_free(xplist) < 0)
goto done; goto done;
@ -754,6 +816,11 @@ xpath_exec(char *xpath,
/*! Intermediate xpath function to handle 'conditional' cases. /*! Intermediate xpath function to handle 'conditional' cases.
* @param[in] xcur xml-tree where to search
* @param[in] xpath string with XPATH syntax
* @param[in] flags if != 0, only match xml nodes matching flags
* @param[in] vec1 vector of XML trees
* @param[in] vec1len length of XML trees
* For example: xpath = //a | //b. * For example: xpath = //a | //b.
* xpath_first+ splits xpath up in several subcalls * xpath_first+ splits xpath up in several subcalls
* (eg xpath=//a and xpath=//b) and collects the results. * (eg xpath=//a and xpath=//b) and collects the results.
@ -762,7 +829,7 @@ xpath_exec(char *xpath,
* Note, this could be 'folded' into xpath1 but I judged it too complex. * Note, this could be 'folded' into xpath1 but I judged it too complex.
*/ */
static int static int
xpath_choice(cxobj *xtop, xpath_choice(cxobj *xcur,
char *xpath0, char *xpath0,
uint16_t flags, uint16_t flags,
cxobj ***vec1, cxobj ***vec1,
@ -776,7 +843,6 @@ xpath_choice(cxobj *xtop,
cxobj **vec0 = NULL; cxobj **vec0 = NULL;
size_t vec0len = 0; size_t vec0len = 0;
if ((s0 = strdup(xpath0)) == NULL){ if ((s0 = strdup(xpath0)) == NULL){
clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__); clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__);
goto done; goto done;
@ -786,7 +852,7 @@ xpath_choice(cxobj *xtop,
clicon_err(OE_UNIX, errno, "calloc"); clicon_err(OE_UNIX, errno, "calloc");
goto done; goto done;
} }
vec0[0] = xtop; vec0[0] = xcur;
vec0len++; vec0len++;
while (s1 != NULL){ while (s1 != NULL){
s2 = strstr(s1, " | "); s2 = strstr(s1, " | ");
@ -796,7 +862,7 @@ xpath_choice(cxobj *xtop,
} }
xpath = s1; xpath = s1;
s1 = s2; s1 = s2;
if (xpath_exec(xpath, vec0, vec0len, flags, vec1, vec1len) < 0) if (xpath_exec(xcur, xpath, vec0, vec0len, flags, vec1, vec1len) < 0)
goto done; goto done;
} }
retval = 0; retval = 0;
@ -808,32 +874,35 @@ xpath_choice(cxobj *xtop,
return retval; return retval;
} }
/*! Help function to xpath_first
*/
static cxobj * static cxobj *
xpath_first0(cxobj *cxtop, xpath_first0(cxobj *xcur,
char *xpath) char *xpath)
{ {
cxobj **vec0 = NULL; cxobj **vec1 = NULL;
size_t vec0len = 0; size_t vec1len = 0;
cxobj *xn = NULL; cxobj *xn = NULL;
if (xpath_choice(cxtop, xpath, 0, &vec0, &vec0len) < 0) if (xpath_choice(xcur, xpath, 0, &vec1, &vec1len) < 0)
goto done; goto done;
if (vec0len) if (vec1len)
xn = vec0[0]; xn = vec1[0];
else else
xn = NULL; xn = NULL;
done: done:
if (vec0) if (vec1)
free(vec0); free(vec1);
return xn; return xn;
} }
/*! A restricted xpath function where the first matching entry is returned /*! A restricted xpath function where the first matching entry is returned
* See xpath1() on details for subset. * See xpath1() on details for subset.
* args: * args:
* @param[in] cxtop xml-tree where to search * @param[in] xcur xml-tree where to search
* @param[in] xpath string with XPATH syntax * @param[in] xpath string with XPATH syntax
* @retval xml-tree of first match, or NULL on error. * @retval xml-tree of first match
* @retval NULL Error or not found
* *
* @code * @code
* cxobj *x; * cxobj *x;
@ -846,7 +915,7 @@ xpath_first0(cxobj *cxtop,
* @see also xpath_vec. * @see also xpath_vec.
*/ */
cxobj * cxobj *
xpath_first(cxobj *cxtop, xpath_first(cxobj *xcur,
char *format, char *format,
...) ...)
{ {
@ -871,7 +940,7 @@ xpath_first(cxobj *cxtop,
goto done; goto done;
} }
va_end(ap); va_end(ap);
retval = xpath_first0(cxtop, xpath); retval = xpath_first0(xcur, xpath);
done: done:
if (xpath) if (xpath)
free(xpath); free(xpath);
@ -881,14 +950,14 @@ xpath_first(cxobj *cxtop,
/*! A restricted xpath iterator that loops over all matching entries. Dont use. /*! A restricted xpath iterator that loops over all matching entries. Dont use.
* *
* See xpath1() on details for subset. * See xpath1() on details for subset.
* @param[in] cxtop xml-tree where to search * @param[in] xcur xml-tree where to search
* @param[in] xpath string with XPATH syntax * @param[in] xpath string with XPATH syntax
* @param[in] xprev iterator/result should be initiated to NULL * @param[in] xprev iterator/result should be initiated to NULL
* @retval xml-tree of n:th match, or NULL on error. * @retval xml-tree of n:th match, or NULL on error.
* *
* @code * @code
* cxobj *x = NULL; * cxobj *x = NULL;
* while ((x = xpath_each(cxtop, "//symbol/foo", x)) != NULL) { * while ((x = xpath_each(xcur, "//symbol/foo", x)) != NULL) {
* ... * ...
* } * }
* @endcode * @endcode
@ -899,33 +968,33 @@ xpath_first(cxobj *cxtop,
* NOTE: uses a static variable: consider replacing with xpath_vec() instead * NOTE: uses a static variable: consider replacing with xpath_vec() instead
*/ */
cxobj * cxobj *
xpath_each(cxobj *cxtop, xpath_each(cxobj *xcur,
char *xpath, char *xpath,
cxobj *xprev) cxobj *xprev)
{ {
static cxobj **vec0 = NULL; /* XXX */ static cxobj **vec1 = NULL; /* XXX */
static size_t vec0len = 0; static size_t vec1len = 0;
cxobj *xn = NULL; cxobj *xn = NULL;
int i; int i;
if (xprev == NULL){ if (xprev == NULL){
if (vec0) // XXX if (vec1) // XXX
free(vec0); // XXX free(vec1); // XXX
vec0len = 0; vec1len = 0;
if (xpath_choice(cxtop, xpath, 0, &vec0, &vec0len) < 0) if (xpath_choice(xcur, xpath, 0, &vec1, &vec1len) < 0)
goto done; goto done;
} }
if (vec0len){ if (vec1len){
if (xprev==NULL) if (xprev==NULL)
xn = vec0[0]; xn = vec1[0];
else{ else{
for (i=0; i<vec0len; i++) for (i=0; i<vec1len; i++)
if (vec0[i] == xprev) if (vec1[i] == xprev)
break; break;
if (i>=vec0len-1) if (i>=vec1len-1)
xn = NULL; xn = NULL;
else else
xn = vec0[i+1]; xn = vec1[i+1];
} }
} }
else else
@ -937,7 +1006,7 @@ xpath_each(cxobj *cxtop,
/*! A restricted xpath that returns a vector of matches /*! A restricted xpath that returns a vector of matches
* *
* See xpath1() on details for subset * See xpath1() on details for subset
. * @param[in] cxtop xml-tree where to search . * @param[in] xcur xml-tree where to search
* @param[in] xpath string with XPATH syntax * @param[in] xpath string with XPATH syntax
* @param[out] vec vector of xml-trees. Vector must be free():d after use * @param[out] vec vector of xml-trees. Vector must be free():d after use
* @param[out] veclen returns length of vector in return value * @param[out] veclen returns length of vector in return value
@ -947,7 +1016,7 @@ xpath_each(cxobj *cxtop,
* @code * @code
* cxobj **xvec; * cxobj **xvec;
* size_t xlen; * size_t xlen;
* if (xpath_vec(cxtop, "//symbol/foo", &xvec, &xlen) < 0) * if (xpath_vec(xcur, "//symbol/foo", &xvec, &xlen) < 0)
* goto err; * goto err;
* for (i=0; i<xlen; i++){ * for (i=0; i<xlen; i++){
* xn = xvec[i]; * xn = xvec[i];
@ -960,7 +1029,7 @@ xpath_each(cxobj *cxtop,
* @see also xpath_first, xpath_each. * @see also xpath_first, xpath_each.
*/ */
int int
xpath_vec(cxobj *cxtop, xpath_vec(cxobj *xcur,
char *format, char *format,
cxobj ***vec, cxobj ***vec,
size_t *veclen, size_t *veclen,
@ -989,7 +1058,7 @@ xpath_vec(cxobj *cxtop,
va_end(ap); va_end(ap);
*vec = NULL; *vec = NULL;
*veclen = 0; *veclen = 0;
retval = xpath_choice(cxtop, xpath, 0x0, vec, veclen); retval = xpath_choice(xcur, xpath, 0x0, vec, veclen);
done: done:
if (xpath) if (xpath)
free(xpath); free(xpath);
@ -997,7 +1066,7 @@ xpath_vec(cxobj *cxtop,
} }
/* A restricted xpath that returns a vector of matches (only nodes marked with flags) /* A restricted xpath that returns a vector of matches (only nodes marked with flags)
* @param[in] cxtop xml-tree where to search * @param[in] xcur xml-tree where to search
* @param[in] xpath string with XPATH syntax * @param[in] xpath string with XPATH syntax
* @param[in] flags Set of flags that return nodes must match (0 if all) * @param[in] flags Set of flags that return nodes must match (0 if all)
* @param[out] vec vector of xml-trees. Vector must be free():d after use * @param[out] vec vector of xml-trees. Vector must be free():d after use
@ -1007,7 +1076,7 @@ xpath_vec(cxobj *cxtop,
* @code * @code
* cxobj **vec; * cxobj **vec;
* size_t veclen; * size_t veclen;
* if (xpath_vec_flag(cxtop, "//symbol/foo", XML_FLAG_ADD, &vec, &veclen) < 0) * if (xpath_vec_flag(xcur, "//symbol/foo", XML_FLAG_ADD, &vec, &veclen) < 0)
* goto err; * goto err;
* for (i=0; i<veclen; i++){ * for (i=0; i<veclen; i++){
* xn = vec[i]; * xn = vec[i];
@ -1020,7 +1089,7 @@ xpath_vec(cxobj *cxtop,
* @see also xpath_vec This is a specialized version. * @see also xpath_vec This is a specialized version.
*/ */
int int
xpath_vec_flag(cxobj *cxtop, xpath_vec_flag(cxobj *xcur,
char *format, char *format,
uint16_t flags, uint16_t flags,
cxobj ***vec, cxobj ***vec,
@ -1050,7 +1119,7 @@ xpath_vec_flag(cxobj *cxtop,
va_end(ap); va_end(ap);
*vec=NULL; *vec=NULL;
*veclen = 0; *veclen = 0;
retval = xpath_choice(cxtop, xpath, flags, vec, veclen); retval = xpath_choice(xcur, xpath, flags, vec, veclen);
done: done:
if (xpath) if (xpath)
free(xpath); free(xpath);

View file

@ -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,47 +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 (schemanode){
if ((yc = yang_find_schemanode((yang_node*)ys, name)) != NULL)
return yc;
}
else
if ((yc = yang_find_datanode((yang_node*)ys, name)) != NULL) if ((yc = yang_find_datanode((yang_node*)ys, name)) != NULL)
return yc; return yc;
} }
return NULL; return NULL;
} }
/*! Find a child spec-node yang_stmt with matching argument for schema-nodid /*! Given a yang statement, find the prefix associated to this module
* * @param[in] ys Yang statement
* See also yang_find() but this looks only for the yang specification nodes with * @retval NULL Not found
* the following keyword: container, leaf, list, leaf-list * @retval prefix Prefix as char* pointer into yang tree
* That is, basic syntax nodes.
* @see yang_find_datanode
* @see clicon_dbget_xpath
*/ */
static yang_stmt * char *
schema_nodeid_stmt(yang_node *yn, yang_find_myprefix(yang_stmt *ys)
char *argument)
{ {
yang_stmt *ys = NULL; yang_stmt *ymod; /* My module */
int i; yang_stmt *yprefix;
int match = 0; char *prefix = NULL;
for (i=0; i<yn->yn_len; i++){ if ((ymod = ys_module(ys)) == NULL){
ys = yn->yn_stmt[i]; clicon_err(OE_YANG, 0, "My yang module not found");
if (!yang_schemanode(ys)) goto done;
continue;
/* some keys dont have arguments, match on key */
if (ys->ys_keyword == Y_INPUT || ys->ys_keyword == Y_OUTPUT){
if (strcmp(argument, yang_key2str(ys->ys_keyword)) == 0){
match++;
break;
} }
} else if ((yprefix = yang_find((yang_node*)ymod, Y_PREFIX, NULL)) == NULL){
if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0){ clicon_err(OE_YANG, 0, "No prefix in my module");
match++; goto done;
break;
} }
} prefix = yprefix->ys_argument;
return match ? ys : NULL; 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,
@ -540,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 */
@ -607,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
@ -623,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;
} }
@ -1074,7 +1129,7 @@ ys_grouping_resolve(yang_stmt *ys,
The target node MUST be either a container, list, choice, case, input, The target node MUST be either a container, list, choice, case, input,
output, or notification node. output, or notification node.
If the "augment" statement is on the top level the absolute form MUST be used. If the "augment" statement is on the top level the absolute form MUST be used.
XXX: Destructively changing a datamodel may affect outlying loop? @note Destructively changing a datamodel may affect outlying loop?
*/ */
static int static int
yang_augment_node(yang_stmt *ys, yang_augment_node(yang_stmt *ys,
@ -1082,7 +1137,7 @@ yang_augment_node(yang_stmt *ys,
{ {
int retval = -1; int retval = -1;
char *schema_nodeid; char *schema_nodeid;
yang_node *yn; yang_stmt *yss = NULL;
yang_stmt *yc; yang_stmt *yc;
int i; int i;
@ -1090,21 +1145,21 @@ yang_augment_node(yang_stmt *ys,
clicon_debug(1, "%s %s", __FUNCTION__, schema_nodeid); clicon_debug(1, "%s %s", __FUNCTION__, schema_nodeid);
/* Find the target */ /* Find the target */
if ((yn = yang_abs_schema_nodeid((yang_node*)ys, schema_nodeid)) == NULL){ if (yang_abs_schema_nodeid(ysp, schema_nodeid, &yss) < 0)
clicon_err(OE_YANG, 0, "Augment schema_nodeid %s not found", schema_nodeid);
// retval = 0; /* Ignore, continue */
goto done; goto done;
} if (yss == NULL)
/* Extend yn with ys' children goto ok;
* First enlarge yn vector /* Extend yss with ys' children
* First enlarge yss vector
*/ */
for (i=0; i<ys->ys_len; i++){ for (i=0; i<ys->ys_len; i++){
if ((yc = ys_dup(ys->ys_stmt[i])) == NULL) if ((yc = ys_dup(ys->ys_stmt[i])) == NULL)
goto done; goto done;
/* XXX: use prefix of origin */ /* XXX: use prefix of origin */
if (yn_insert(yn, yc) < 0) if (yn_insert((yang_node*)yss, yc) < 0)
goto done; goto done;
} }
ok:
retval = 0; retval = 0;
done: done:
return retval; return retval;
@ -1126,10 +1181,14 @@ yang_augment_spec(yang_spec *ysp)
j = 0; j = 0;
while (j<ym->ys_len){ /* Top-level symbols in modules */ while (j<ym->ys_len){ /* Top-level symbols in modules */
ys = ym->ys_stmt[j++]; ys = ym->ys_stmt[j++];
if (ys->ys_keyword != Y_AUGMENT) switch (ys->ys_keyword){
continue; case Y_AUGMENT: /* top-level */
if (yang_augment_node(ys, ysp) < 0) if (yang_augment_node(ys, ysp) < 0)
goto done; goto done;
break;
default:
break;
}
} }
} }
retval = 0; retval = 0;
@ -1498,6 +1557,49 @@ yang_parse_recurse(clicon_handle h,
return ymod; /* top-level (sub)module */ return ymod; /* top-level (sub)module */
} }
int
ys_schemanode_check(yang_stmt *ys,
void *arg)
{
int retval = -1;
yang_spec *yspec;
yang_stmt *yres;
yang_node *yp;
yp = ys->ys_parent;
switch (ys->ys_keyword){
case Y_AUGMENT:
if (yp->yn_keyword == Y_MODULE) /* Not top-level */
break;
/* fallthru */
case Y_REFINE:
case Y_UNIQUE:
if (yang_desc_schema_nodeid(yp, ys->ys_argument, &yres) < 0)
goto done;
if (yres == NULL){
clicon_err(OE_YANG, 0, "schemanode sanity check of %d %s",
ys->ys_keyword,
ys->ys_argument);
goto done;
}
break;
case Y_DEVIATION:
yspec = ys_spec(ys);
if (yang_abs_schema_nodeid(yspec, ys->ys_argument, &yres) < 0)
goto done;
if (yres == NULL){
clicon_err(OE_YANG, 0, "schemanode sanity check of %s", ys->ys_argument);
goto done;
}
break;
default:
break;
}
retval = 0;
done:
return retval;
}
/*! Parse top yang module including all its sub-modules. Expand and populate yang tree /*! Parse top yang module including all its sub-modules. Expand and populate yang tree
* *
* @param[in] h CLICON handle * @param[in] h CLICON handle
@ -1552,12 +1654,14 @@ yang_parse(clicon_handle h,
goto done; goto done;
yang_apply((yang_node*)ymod, -1, ys_flag_reset, (void*)YANG_FLAG_MARK); yang_apply((yang_node*)ymod, -1, ys_flag_reset, (void*)YANG_FLAG_MARK);
/* Step 4: Top-level augmentation of all modules */ /* Step 4: Top-level augmentation of all modules */
if (yang_augment_spec(ysp) < 0) if (yang_augment_spec(ysp) < 0)
goto done; goto done;
/* sanity check of schemanode references, need more here */
if (yang_apply((yang_node*)ysp, -1, ys_schemanode_check, NULL) < 0)
goto done;
retval = 0; retval = 0;
done: done:
return retval; return retval;
@ -1621,21 +1725,26 @@ yang_apply(yang_node *yn,
/*! All the work for schema_nodeid functions both absolute and descendant /*! All the work for schema_nodeid functions both absolute and descendant
* Ignore prefixes, see _abs * Ignore prefixes, see _abs
* @param[in] yn Yang node * @param[in] yn Yang node. Find next yang stmt and return that if match.
* @param[in] vec Vector of nodeid's in a schema node identifier, eg a/b * @param[in] vec Vector of nodeid's in a schema node identifier, eg a/b
* @param[in] nvec Length of vec * @param[in] nvec Length of vec
* @retval NULL Error, with clicon_err called * @param[out] yres Result yang statement node, or NULL if not found
* @retval yres First yang node matching schema nodeid * @retval -1 Error, with clicon_err called
* @retval 0 OK
*/ */
static yang_node * static int
schema_nodeid_vec(yang_node *yn, schema_nodeid_vec(yang_node *yn,
char **vec, char **vec,
int nvec) int nvec,
yang_stmt **yres)
{ {
int retval = -1;
char *arg; char *arg;
yang_node *ynext;
char *nodeid;
int i;
yang_stmt *ys; yang_stmt *ys;
yang_node *yres = NULL; int match;
char *id;
if (nvec <= 0) if (nvec <= 0)
goto done; goto done;
@ -1644,88 +1753,129 @@ schema_nodeid_vec(yang_node *yn,
__FUNCTION__, yang_key2str(yn->yn_keyword), yn->yn_argument, __FUNCTION__, yang_key2str(yn->yn_keyword), yn->yn_argument,
arg, yn->yn_len); arg, yn->yn_len);
if (strcmp(arg, "..") == 0) if (strcmp(arg, "..") == 0)
ys = (yang_stmt*)yn->yn_parent; ynext = yn->yn_parent; /* This could actually be a MODULE */
else{ else{
/* ignore prefixes */ /* ignore prefixes */
if ((id = strchr(arg, ':')) == NULL) if ((nodeid = strchr(arg, ':')) == NULL)
id = arg; nodeid = arg;
else else
id++; nodeid++;
if ((ys = schema_nodeid_stmt(yn, id)) == NULL){ match = 0;
clicon_debug(1, "%s %s not found", __FUNCTION__, id); ys = NULL;
for (i=0; i<yn->yn_len; i++){
ys = yn->yn_stmt[i];
if (!yang_schemanode(ys))
continue;
/* some keys dont have arguments, match on key */
if (ys->ys_keyword == Y_INPUT || ys->ys_keyword == Y_OUTPUT){
if (strcmp(nodeid, yang_key2str(ys->ys_keyword)) == 0){
match++;
break;
}
} else
if (ys->ys_argument && strcmp(nodeid, ys->ys_argument) == 0){
match++;
break;
}
}
if (!match){
clicon_debug(1, "%s not found", nodeid);
goto ok;
}
ynext = (yang_node*)ys;
}
if (nvec == 1){ /* match */
if (yang_schemanode((yang_stmt*)ynext))
*yres = (yang_stmt*)ynext;
else
clicon_debug(1, "%s not schema node", nodeid);
goto ok;
}
/* recursive call using ynext */
if (schema_nodeid_vec(ynext, vec+1, nvec-1, yres) < 0)
goto done; goto done;
} ok:
} retval = 0;
if (nvec == 1){
yres = (yang_node*)ys;
goto done;
}
yres = schema_nodeid_vec((yang_node*)ys, vec+1, nvec-1);
done: done:
return yres; return retval;
} }
/*! Given an absolute schema-nodeid (eg /a/b/c) find matching yang spec /*! Given an absolute schema-nodeid (eg /a/b/c) find matching yang spec
* @param[in] yn Yang node * @param[in] yspec Yang specification.
* @param[in] schema_nodeid Absolute schema-node-id, ie /a/b * @param[in] schema_nodeid Absolute schema-node-id, ie /a/b
* @retval NULL Error, with clicon_err called * @retval NULL Error, with clicon_err called
* @retval yres First yang node matching schema nodeid * @retval yres First yang node matching schema nodeid
* @see yang_schema_nodeid * Assume schema nodeid:s have prefixes, (actually the first).
* @see yang_desc_schema_nodeid
* Used in yang: deviation, top-level augment
*/ */
yang_node * int
yang_abs_schema_nodeid(yang_node *yn, yang_abs_schema_nodeid(yang_spec *yspec,
char *schema_nodeid) char *schema_nodeid,
yang_stmt **yres)
{ {
int retval = -1;
char **vec = NULL; char **vec = NULL;
int nvec; int nvec;
yang_node *yres = NULL;
yang_stmt *ymod; yang_stmt *ymod;
char *id; char *id;
char *prefix = NULL; char *prefix = NULL;
yang_stmt *yprefix;
/* check absolute schema_nodeid */
if (schema_nodeid[0] != '/'){
clicon_err(OE_YANG, EINVAL, "absolute schema nodeid should start with /");
goto done;
}
if ((vec = clicon_strsep(schema_nodeid, "/", &nvec)) == NULL){ if ((vec = clicon_strsep(schema_nodeid, "/", &nvec)) == NULL){
clicon_err(OE_YANG, errno, "%s: strsep", __FUNCTION__); clicon_err(OE_YANG, errno, "%s: strsep", __FUNCTION__);
goto done; goto done;
} }
/* Assume schame nodeid looks like: "/prefix:id[/prefix:id]*" */ /* Assume schema nodeid looks like: "/prefix:id[/prefix:id]*" */
if (nvec < 2){ if (nvec < 2){
clicon_err(OE_YANG, 0, "%s: NULL or truncated path: %s", clicon_err(OE_YANG, 0, "%s: NULL or truncated path: %s",
__FUNCTION__, schema_nodeid); __FUNCTION__, schema_nodeid);
goto done; goto done;
} }
/* Check for absolute path */
if (strcmp(vec[0], "") != 0){
clicon_err(OE_YANG, errno, "%s: %s: expected absolute path",
__FUNCTION__, vec[0]);
goto done;
}
/* Find correct module */
if ((ymod = ys_module((yang_stmt*)yn)) == NULL){
clicon_err(OE_YANG, 0, "Could not find top-level");
goto done;
}
/* split <prefix>:<id> */ /* split <prefix>:<id> */
if ((id = strchr(vec[1], ':')) == NULL){ if ((id = strchr(vec[1], ':')) == NULL){ /* no prefix */
id = vec[1]; clicon_err(OE_YANG, 0, "Absolute schema nodeid must have prefix");
goto done;
} }
else{ /* other module - peek into first element to find module */
if ((prefix = strdup(vec[1])) == NULL){ if ((prefix = strdup(vec[1])) == NULL){
clicon_err(OE_UNIX, errno, "%s: strdup", __FUNCTION__); clicon_err(OE_UNIX, errno, "%s: strdup", __FUNCTION__);
goto done; goto done;
} }
prefix[id-vec[1]] = '\0'; prefix[id-vec[1]] = '\0';
id++; id++;
if ((ymod = yang_find_module_by_prefix((yang_stmt*)yn, prefix)) == NULL) ymod = NULL;
while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) {
if ((yprefix = yang_find((yang_node*)ymod, Y_PREFIX, NULL)) != NULL &&
strcmp(yprefix->ys_argument, prefix) == 0){
break;
}
}
if (ymod == NULL){ /* Try with topnode */
yang_stmt *ys;
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; goto done;
} }
yres = schema_nodeid_vec((yang_node*)ymod, vec+1, nvec-1); 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)
goto done;
retval = 0;
done: done:
if (vec) if (vec)
free(vec); free(vec);
if (prefix) if (prefix)
free(prefix); free(prefix);
return yres; return retval;
} }
/*! Given a descendant schema-nodeid (eg a/b/c) find matching yang spec /*! Given a descendant schema-nodeid (eg a/b/c) find matching yang spec
@ -1734,12 +1884,14 @@ yang_abs_schema_nodeid(yang_node *yn,
* @retval NULL Error, with clicon_err called * @retval NULL Error, with clicon_err called
* @retval yres First yang node matching schema nodeid * @retval yres First yang node matching schema nodeid
* @see yang_schema_nodeid * @see yang_schema_nodeid
* Used in yang: unique, refine, uses augment
*/ */
yang_node * int
yang_desc_schema_nodeid(yang_node *yn, yang_desc_schema_nodeid(yang_node *yn,
char *schema_nodeid) char *schema_nodeid,
yang_stmt **yres)
{ {
yang_node *yres = NULL; int retval = -1;
char **vec = NULL; char **vec = NULL;
int nvec; int nvec;
@ -1747,47 +1899,18 @@ yang_desc_schema_nodeid(yang_node *yn,
goto done; goto done;
/* check absolute schema_nodeid */ /* check absolute schema_nodeid */
if (schema_nodeid[0] == '/'){ if (schema_nodeid[0] == '/'){
clicon_err(OE_YANG, EINVAL, "descenadant schema nodeid should not start with /"); clicon_err(OE_YANG, EINVAL, "descendant schema nodeid should not start with /");
goto done; goto done;
} }
if ((vec = clicon_strsep(schema_nodeid, "/", &nvec)) == NULL) if ((vec = clicon_strsep(schema_nodeid, "/", &nvec)) == NULL)
goto done; goto done;
yres = schema_nodeid_vec((yang_node*)yn, vec, nvec); if (schema_nodeid_vec(yn, vec, nvec, yres) < 0)
goto done;
retval = 0;
done: done:
if (vec) if (vec)
free(vec); free(vec);
return yres; return retval;
}
/*! Given a schema-node-id (eg /a/b/c or a/b/c) find matching yang specification
*
* They are either absolute (start with /) or descendant (no /)
* Compare with XPATH for XML, this is for YANG spec
* @param[in] yn Yang node tree
* @param[in] schema_nodeid schema-node-id, ie a/b or /a/b
* @retval NULL Error, with clicon_err called
* @retval yres First yang node matching schema nodeid
* @note: the identifiers in the xpath (eg a, b in a/b) can match the nodes
* defined in yang_xpath: container, leaf,list,leaf-list, modules, sub-modules
* Example:
* yn : module m { prefix b; container b { list c { key d; leaf d; }} }
* schema-node-id = m/b/c, returns the list 'c'.
*/
yang_node *
yang_schema_nodeid(yang_node *yn,
char *schema_nodeid)
{
yang_node *yres = NULL;
if (strlen(schema_nodeid) == 0)
goto done;
/* check absolute schema_nodeid */
if (schema_nodeid[0] == '/')
yres = yang_abs_schema_nodeid(yn, schema_nodeid);
else
yres = yang_desc_schema_nodeid(yn, schema_nodeid);
done:
return yres;
} }
/*! Parse argument as CV and save result in yang cv variable /*! Parse argument as CV and save result in yang cv variable

View file

@ -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,9 +228,12 @@ 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)
@ -1041,3 +1046,4 @@ yang_type_get(yang_stmt *ys,
done: done:
return retval; return retval;
} }

View file

@ -40,7 +40,7 @@ expectfn(){
return return
fi fi
# grep extended grep # grep extended grep
match=`echo "$ret" | grep -EZo "$expect"` match=`echo $ret | grep -EZo "$expect"`
# echo "ret:\"$ret\"" # echo "ret:\"$ret\""
# echo "expect:\"$expect\"" # echo "expect:\"$expect\""
# echo "match:\"$match\"" # echo "match:\"$match\""

View file

@ -27,14 +27,13 @@ module example{
} }
leaf address { leaf address {
type leafref { type leafref {
path "../../interfaces/interface[name=eth0]" path "../../interfaces/interface[name = current()/../relname]"
+ "/address/ip"; + "/ipv4/address/ip";
} }
} }
} }
} }
EOF EOF
# XXX not eth0 path "../../interface[name = current()/../ifname]"
# kill old backend (if any) # kill old backend (if any)
new "kill old backend" new "kill old backend"

View file

@ -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"

View file

@ -30,58 +30,68 @@ new "restconf options"
expectfn "curl -i -sS -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE" expectfn "curl -i -sS -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE"
new "restconf head" new "restconf head"
expectfn "curl -sS -I http://localhost/restconf/data" "Content-Type: application/yang.data\+json" expectfn "curl -sS -I http://localhost/restconf/data" "HTTP/1.1 200 OK"
#Content-Type: application/yang-data+json"
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 json"
expectfn 'curl -sS -X POST -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/rt:fib-route' '{ "output": { "route": { "address-family": "ipv4", "next-hop": { "next-hop-list": "2.3.4.5" } } } } '
new "restconf rpc using POST xml"
ret=$(curl -sS -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route)
expect="<output> <route> <address-family>ipv4</address-family> <next-hop> <next-hop-list>2.3.4.5</next-hop-li t> </next-hop> </route> </output> "
match=`echo $ret | grep -EZo "$expect"`
echo -n "ret: "
echo $ret
new "Kill restconf daemon" new "Kill restconf daemon"
sudo pkill -u www-data clixon_restconf sudo pkill -u www-data clixon_restconf

View file

@ -1,5 +1,6 @@
#!/bin/bash #!/bin/bash
# Advanced union types and generated code # Advanced union types and generated code
# and enum w values
# include err() and new() functions # include err() and new() functions
. ./lib.sh . ./lib.sh
@ -48,6 +49,14 @@ module example{
type af; type af;
} }
} }
leaf status {
type enumeration {
enum up {
value 1;
}
enum down;
}
}
} }
EOF EOF
@ -101,6 +110,9 @@ expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/type.yang" "<rpc><discard-chan
new "netconf commit" new "netconf commit"
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/type.yang" "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/type.yang" "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "cli enum value"
expectfn "$clixon_cli -1f $clixon_cf -l o -y /tmp/type.yang set status down" "^$"
new "Kill backend" new "Kill backend"
# Check if still alive # Check if still alive
pid=`pgrep clixon_backend` pid=`pgrep clixon_backend`