Added restconf/operations get, see RFC8040 Sec 3.3.2:
This commit is contained in:
Olof hagsand 2018-03-04 21:24:38 +01:00
parent 6a22524d38
commit 990700b68d
16 changed files with 355 additions and 93 deletions

View file

@ -1,14 +1,13 @@
# Clixon Changelog # Clixon Changelog
## 3.6.0 (Upcoming) ## 3.6.0 (Upcoming)
### Major changes:
### Minor changes:
### Corrected Bugs
* Translate xml->json \n correctly
### Major changes: ### Major changes:
### Minor changes: ### Minor changes:
* yang_find_topnode() and api_path2xml() schemanode parameter replaced with yang_class. Replace as follows: 0 -> YC_DATANODE, 1 -> YC_SCHEMANODE
* xml2json: include prefix in translation, so <a:b> is translated to {"a:b" ..}
* Use <config> instead of <data> when save/load configuration to file. This * Use <config> instead of <data> when save/load configuration to file. This
enables saved files to be used as datastore without any editing. Thanks Matt. enables saved files to be used as datastore without any editing. Thanks Matt.
@ -21,6 +20,7 @@ enables saved files to be used as datastore without any editing. Thanks Matt.
* Added cli_show_version() * Added cli_show_version()
### Corrected Bugs ### Corrected Bugs
* Translate xml->json \n correctly
* Fix issue: https://github.com/clicon/clixon/issues/15 Replace whole config * Fix issue: https://github.com/clicon/clixon/issues/15 Replace whole config
## 3.5.0 (12 February 2018) ## 3.5.0 (12 February 2018)

View file

@ -236,7 +236,7 @@ cli_dbxml(clicon_handle h,
if ((xtop = xml_new("config", NULL, NULL)) == NULL) if ((xtop = xml_new("config", NULL, NULL)) == NULL)
goto done; goto done;
xbot = xtop; xbot = xtop;
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0)
goto done; goto done;
if ((xa = xml_new("operation", xbot, NULL)) == NULL) if ((xa = xml_new("operation", xbot, NULL)) == NULL)
goto done; goto done;

View file

@ -171,7 +171,7 @@ expand_dbvar(void *h,
/* This is primarily to get "y", /* This is primarily to get "y",
* xpath2xml would have worked!! * xpath2xml would have worked!!
*/ */
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &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

@ -131,11 +131,12 @@ api_data(clicon_handle h,
/*! Operations REST method, POST /*! Operations REST method, POST
* @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] path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec * @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] dvec Stream input data * @param[in] data Stream input data
* @param[in] username Authenticated user
*/ */
static int static int
api_operations(clicon_handle h, api_operations(clicon_handle h,
@ -154,16 +155,42 @@ api_operations(clicon_handle h,
request_method = FCGX_GetParam("REQUEST_METHOD", r->envp); request_method = FCGX_GetParam("REQUEST_METHOD", r->envp);
clicon_debug(1, "%s method:%s", __FUNCTION__, request_method); clicon_debug(1, "%s method:%s", __FUNCTION__, request_method);
if (strcmp(request_method, "GET")==0) if (strcmp(request_method, "GET")==0)
retval = api_operation_get(h, r, path, pcvec, pi, qvec, data, username); retval = api_operations_get(h, r, path, pcvec, pi, qvec, data, username);
else if (strcmp(request_method, "POST")==0) else if (strcmp(request_method, "POST")==0)
retval = api_operation_post(h, r, path, pcvec, pi, qvec, data, username); retval = api_operations_post(h, r, path, pcvec, pi, qvec, data, username);
else else
retval = notfound(r); retval = notfound(r);
return retval; return retval;
} }
/*! Determine the root of the RESTCONF API
* @param[in] h Clicon handle
* @param[in] r Fastcgi request handle
* @note Hardcoded to "/restconf"
* Return see RFC8040 3.1 and RFC7320
* In line with the best practices defined by [RFC7320], RESTCONF
* enables deployments to specify where the RESTCONF API is located.
*/
static int
api_well_known(clicon_handle h,
FCGX_Request *r)
{
clicon_debug(1, "%s", __FUNCTION__);
FCGX_FPrintF(r->out, "Content-Type: application/xrd+xml\r\n");
FCGX_FPrintF(r->out, "\r\n");
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>\r\n");
FCGX_FPrintF(r->out, " <Link rel='restconf' href='/restconf'/>\r\n");
FCGX_FPrintF(r->out, "</XRD>\r\n");
return 0;
}
/*! Retrieve the Top-Level API Resource /*! Retrieve the Top-Level API Resource
* @param[in] h Clicon handle
* @param[in] r Fastcgi request handle
* @note Only returns null for operations and data,... * @note Only returns null for operations and data,...
* See RFC8040 3.3
*/ */
static int static int
api_root(clicon_handle h, api_root(clicon_handle h,
@ -181,9 +208,11 @@ api_root(clicon_handle h,
media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp);
if (strcmp(media_accept, "application/yang-data+xml")==0) if (strcmp(media_accept, "application/yang-data+xml")==0)
use_xml++; use_xml++;
clicon_debug(1, "%s use-xml:%d media-accept:%s", __FUNCTION__, use_xml, media_accept);
FCGX_SetExitStatus(200, r->out); /* OK */ 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, "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 (xml_parse_string("<restconf><data></data><operations></operations><yang-library-version>2016-06-21</yang-library-version></restconf>", NULL, &xt) < 0) if (xml_parse_string("<restconf><data></data><operations></operations><yang-library-version>2016-06-21</yang-library-version></restconf>", NULL, &xt) < 0)
goto done; goto done;
if ((cb = cbuf_new()) == NULL){ if ((cb = cbuf_new()) == NULL){
@ -298,6 +327,8 @@ api_restconf(clicon_handle h,
retval = notfound(r); retval = notfound(r);
goto done; goto done;
} }
test(r, 1);
if (pn == 2){ if (pn == 2){
retval = api_root(h, r); retval = api_root(h, r);
goto done; goto done;
@ -319,7 +350,6 @@ api_restconf(clicon_handle h,
if (str2cvec(data, '&', '=', &dvec) < 0) if (str2cvec(data, '&', '=', &dvec) < 0)
goto done; goto done;
test(r, 1);
/* If present, check credentials. See "plugin_credentials" in plugin /* If present, check credentials. See "plugin_credentials" in plugin
* See RFC 8040 section 2.5 * See RFC 8040 section 2.5
*/ */
@ -367,23 +397,6 @@ api_restconf(clicon_handle h,
return retval; return retval;
} }
/*! Process a FastCGI request
* @param[in] r Fastcgi request handle
*/
static int
api_well_known(clicon_handle h,
FCGX_Request *r)
{
clicon_debug(1, "%s", __FUNCTION__);
FCGX_FPrintF(r->out, "Content-Type: application/xrd+xml\r\n");
FCGX_FPrintF(r->out, "\r\n");
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>\r\n");
FCGX_FPrintF(r->out, " <Link rel='restconf' href='/restconf'/>\r\n");
FCGX_FPrintF(r->out, "</XRD>\r\n");
return 0;
}
static int static int
restconf_terminate(clicon_handle h) restconf_terminate(clicon_handle h)
@ -547,7 +560,7 @@ main(int argc,
if (strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)) == 0) if (strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)) == 0)
api_restconf(h, r); /* This is the function */ api_restconf(h, r); /* This is the function */
else if (strncmp(path, RESTCONF_WELL_KNOWN, strlen(RESTCONF_WELL_KNOWN)) == 0) { else if (strncmp(path, RESTCONF_WELL_KNOWN, strlen(RESTCONF_WELL_KNOWN)) == 0) {
api_well_known(h, r); /* This is the function */ api_well_known(h, r); /* */
} }
else{ else{
clicon_debug(1, "top-level %s not found", path); clicon_debug(1, "top-level %s not found", path);

View file

@ -195,6 +195,7 @@ api_data_get_err(clicon_handle h,
* @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts * @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] username Authenticated user
* @param[in] head If 1 is HEAD, otherwise GET * @param[in] head If 1 is HEAD, otherwise GET
* @code * @code
* curl -G http://localhost/restconf/data/interfaces/interface=eth0 * curl -G http://localhost/restconf/data/interfaces/interface=eth0
@ -332,6 +333,7 @@ api_data_get2(clicon_handle h,
* @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts * @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] username Authenticated user
The HEAD method is sent by the client to retrieve just the header fields The HEAD method is sent by the client to retrieve just the header fields
that would be returned for the comparable GET method, without the that would be returned for the comparable GET method, without the
response message-body. response message-body.
@ -355,6 +357,7 @@ api_data_head(clicon_handle h,
* @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts * @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] username Authenticated user
* @code * @code
* curl -G http://localhost/restconf/data/interfaces/interface=eth0 * curl -G http://localhost/restconf/data/interfaces/interface=eth0
* @endcode * @endcode
@ -390,6 +393,8 @@ api_data_get(clicon_handle h,
* @param[in] pi Offset, where to start pcvec * @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data * @param[in] data Stream input data
* @param[in] username Authenticated user
* @note restconf POST is mapped to edit-config create. * @note restconf POST is mapped to edit-config create.
POST: POST:
target resource type is datastore --> create a top-level resource target resource type is datastore --> create a top-level resource
@ -461,7 +466,7 @@ api_data_post(clicon_handle h,
goto done; goto done;
} }
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0)
goto done; goto done;
/* Parse input data as json or xml into xml */ /* Parse input data as json or xml into xml */
if (parse_xml){ if (parse_xml){
@ -586,6 +591,7 @@ match_list_keys(yang_stmt *y,
* @param[in] pi Offset, where to start pcvec * @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data * @param[in] data Stream input data
* @param[in] username Authenticated user
* @note restconf PUT is mapped to edit-config replace. * @note restconf PUT is mapped to edit-config replace.
* @example * @example
curl -X PUT -d '{"enabled":"false"}' http://127.0.0.1/restconf/data/interfaces/interface=eth1 curl -X PUT -d '{"enabled":"false"}' http://127.0.0.1/restconf/data/interfaces/interface=eth1
@ -651,7 +657,7 @@ api_data_put(clicon_handle h,
if (xml_value_set(xu, username) < 0) if (xml_value_set(xu, username) < 0)
goto done; goto done;
} }
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0)
goto done; goto done;
/* Parse input data as json or xml into xml */ /* Parse input data as json or xml into xml */
if (parse_xml){ if (parse_xml){
@ -749,6 +755,7 @@ api_data_put(clicon_handle h,
* @param[in] pi Offset, where to start pcvec * @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data * @param[in] data Stream input data
* @param[in] username Authenticated user
* Netconf: <edit-config> (nc:operation="merge") * Netconf: <edit-config> (nc:operation="merge")
*/ */
int int
@ -769,6 +776,7 @@ api_data_patch(clicon_handle h,
* @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.3.1 in rfc8040) * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] username Authenticated user
* @param[in] pi Offset, where path starts * @param[in] pi Offset, where path starts
* Example: * Example:
* curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0 * curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0
@ -812,7 +820,7 @@ api_data_delete(clicon_handle h,
if (xml_value_set(xu, username) < 0) if (xml_value_set(xu, username) < 0)
goto done; goto done;
} }
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0)
goto done; goto done;
if ((xa = xml_new("operation", xbot, NULL)) == NULL) if ((xa = xml_new("operation", xbot, NULL)) == NULL)
goto done; goto done;
@ -851,10 +859,27 @@ api_data_delete(clicon_handle h,
return retval; return retval;
} }
/*! NYI /*! GET restconf/operations resource
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @param[in] 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 path starts
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* @param[in] username Authenticated user
*
* @code
* curl -G http://localhost/restconf/operations
* @endcode
* RFC8040 Sec 3.3.2:
* This optional resource is a container that provides access to the
* data-model-specific RPC operations supported by the server. The
* server MAY omit this resource if no data-model-specific RPC
* operations are advertised.
*/ */
int int
api_operation_get(clicon_handle h, api_operations_get(clicon_handle h,
FCGX_Request *r, FCGX_Request *r,
char *path, char *path,
cvec *pcvec, cvec *pcvec,
@ -863,23 +888,87 @@ api_operation_get(clicon_handle h,
char *data, char *data,
char *username) char *username)
{ {
notimplemented(r); int retval = -1;
return 0; int pretty;
char *media_accept;
int use_xml = 0; /* By default use JSON */
yang_spec *yspec;
yang_stmt *ym;
yang_stmt *yc;
yang_stmt *yprefix;
char *prefix;
cbuf *cbx = NULL;
cxobj *xt = NULL;
clicon_debug(1, "%s", __FUNCTION__);
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp);
if (strcmp(media_accept, "application/yang-data+xml")==0)
use_xml++;
yspec = clicon_dbspec_yang(h);
if ((cbx = cbuf_new()) == NULL)
goto done;
cprintf(cbx, "<operations>");
ym = NULL;
while ((ym = yn_each((yang_node*)yspec, ym)) != NULL) {
if ((yprefix = yang_find((yang_node*)ym, Y_PREFIX, NULL)) != NULL)
prefix = yprefix->ys_argument;
else
continue;
yc = NULL;
while ((yc = yn_each((yang_node*)ym, yc)) != NULL) {
if (yc->ys_keyword != Y_RPC)
continue;
cprintf(cbx, "<%s:%s />", prefix, yc->ys_argument);
}
}
cprintf(cbx, "</operations>");
clicon_debug(1, "%s xml:%s", __FUNCTION__, cbuf_get(cbx));
if (xml_parse_string(cbuf_get(cbx), yspec, &xt) < 0)
goto done;
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
cbuf_reset(cbx); /* reuse same cbuf */
if (use_xml){
if (clicon_xml2cbuf(cbx, xt, 0, pretty) < 0) /* Dont print top object? */
goto done;
}
else{
if (xml2json_cbuf(cbx, xt, pretty) < 0)
goto done;
}
clicon_debug(1, "%s ret:%s", __FUNCTION__, cbuf_get(cbx));
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");
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
FCGX_FPrintF(r->out, "\r\n\r\n");
// ok:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cbx)
cbuf_free(cbx);
if (xt)
xml_free(xt);
return retval;
} }
/*! REST operation POST method /*! REST operation POST method
* @param[in] h CLIXON handle * @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle * @param[in] r Fastcgi request handle
* @param[in] path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec * @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data * @param[in] data Stream input data
* @note We map post to edit-config create. * @param[in] username Authenticated user
* @note We map post to edit-config create.
POST {+restconf}/operations/<operation> POST {+restconf}/operations/<operation>
*/ */
int int
api_operation_post(clicon_handle h, api_operations_post(clicon_handle h,
FCGX_Request *r, FCGX_Request *r,
char *path, char *path,
cvec *pcvec, cvec *pcvec,
@ -956,7 +1045,7 @@ api_operation_post(clicon_handle h,
} }
/* XXX: something strange for rpc user */ /* XXX: something strange for rpc user */
if (api_path2xml(oppath, yspec, xtop, 1, &xbot, &y) < 0) if (api_path2xml(oppath, yspec, xtop, YC_SCHEMANODE, &xbot, &y) < 0)
goto done; goto done;
#if 1 #if 1
{ {

View file

@ -61,13 +61,13 @@ int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path,
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,
char *username); char *username);
int api_operation_get(clicon_handle h, FCGX_Request *r, int api_operations_get(clicon_handle h, FCGX_Request *r,
char *path,
cvec *pcvec, int pi, cvec *qvec, char *data, char *username);
int api_operation_post(clicon_handle h, FCGX_Request *r,
char *path, char *path,
cvec *pcvec, int pi, cvec *qvec, char *data, cvec *pcvec, int pi, cvec *qvec, char *data, char *username);
char *username);
int api_operations_post(clicon_handle h, FCGX_Request *r,
char *path,
cvec *pcvec, int pi, cvec *qvec, char *data,
char *username);
#endif /* _RESTCONF_METHODS_H_ */ #endif /* _RESTCONF_METHODS_H_ */

View file

@ -312,7 +312,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, 0)) == NULL){ if ((y = yang_find_topnode(ys, name, YC_DATANODE)) == 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;
} }
@ -799,7 +799,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), 0)) == NULL){ if ((ys = yang_find_topnode(yspec, xml_name(x), YC_DATANODE)) == 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

@ -792,7 +792,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, 0)) == NULL){ if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec"); clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done; goto done;
} }

View file

@ -64,7 +64,7 @@ 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, int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop,
int schemanode, cxobj **xpathp, yang_node **ypathp); yang_class nodeclass, 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);

View file

@ -123,14 +123,45 @@ enum rfc_6020{
Y_SPEC /* XXX: NOTE NOT YANG STATEMENT, reserved for top level spec */ Y_SPEC /* XXX: NOTE NOT YANG STATEMENT, reserved for top level spec */
}; };
/* Type used to group yang nodes used in some functions
* See RFC7950 Sec 3
*/
enum yang_class{
YC_NONE, /* Someting else,... */
YC_DATANODE, /* See yang_datanode() */
YC_DATADEFINITION, /* See yang_datadefinition() */
YC_SCHEMANODE /* See yang_schemanode() */
};
typedef enum yang_class yang_class;
#define YANG_FLAG_MARK 0x01 /* Marker for dynamic algorithms, eg expand */ #define YANG_FLAG_MARK 0x01 /* Marker for dynamic algorithms, eg expand */
/* Yang data node */ /* Yang data node
* See RFC7950 Sec 3:
* o data node: A node in the schema tree that can be instantiated in a
* data tree. One of container, leaf, leaf-list, list, anydata, and
* anyxml.
*/
#define yang_datanode(y) ((y)->ys_keyword == Y_CONTAINER || (y)->ys_keyword == Y_LEAF || (y)->ys_keyword == Y_LIST || (y)->ys_keyword == Y_LEAF_LIST || (y)->ys_keyword == Y_ANYXML) #define yang_datanode(y) ((y)->ys_keyword == Y_CONTAINER || (y)->ys_keyword == Y_LEAF || (y)->ys_keyword == Y_LIST || (y)->ys_keyword == Y_LEAF_LIST || (y)->ys_keyword == Y_ANYXML)
/* Yang schema node */ /* Yang data definition statement
* See RFC 7950 Sec 3:
* o data definition statement: A statement that defines new data
* nodes. One of "container", "leaf", "leaf-list", "list", "choice",
* "case", "augment", "uses", "anydata", and "anyxml".
*/
#define yang_datadefinition(y) (yang_datanode(y) || (y)->ys_keyword == Y_CHOICE || (y)->ys_keyword == Y_CASE || (y)->ys_keyword == Y_AUGMENT || (y)->ys_keyword == Y_USES)
/* Yang schema node .
* See RFC 7950 Sec 3:
* o schema node: A node in the schema tree. One of action, container,
* leaf, leaf-list, list, choice, case, rpc, input, output,
* notification, anydata, and anyxml.
*/
#define yang_schemanode(y) (yang_datanode(y) || (y)->ys_keyword == Y_RPC || (y)->ys_keyword == Y_CHOICE || (y)->ys_keyword == Y_CASE || (y)->ys_keyword == Y_INPUT || (y)->ys_keyword == Y_OUTPUT || (y)->ys_keyword == Y_NOTIFICATION) #define yang_schemanode(y) (yang_datanode(y) || (y)->ys_keyword == Y_RPC || (y)->ys_keyword == Y_CHOICE || (y)->ys_keyword == Y_CASE || (y)->ys_keyword == Y_INPUT || (y)->ys_keyword == Y_OUTPUT || (y)->ys_keyword == Y_NOTIFICATION)
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
@ -217,7 +248,7 @@ 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_schemanode(yang_node *yn, char *argument); yang_stmt *yang_find_schemanode(yang_node *yn, char *argument);
yang_stmt *yang_find_topnode(yang_spec *ysp, char *name, int schemanode); yang_stmt *yang_find_topnode(yang_spec *ysp, char *name, yang_class class);
int yang_order(yang_stmt *y); int yang_order(yang_stmt *y);
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);

View file

@ -304,10 +304,12 @@ xml2json1_cbuf(cbuf *cb,
break; break;
} }
case NO_ARRAY: case NO_ARRAY:
if (!flat) if (!flat){
cprintf(cb, "%*s\"%s\": ", cprintf(cb, "%*s\"", pretty?(level*JSON_INDENT):0, "");
pretty?(level*JSON_INDENT):0, "", if (xml_namespace(x))
xml_name(x)); cprintf(cb, "%s:", xml_namespace(x));
cprintf(cb, "%s\": ", xml_name(x));
}
switch (childt){ switch (childt){
case NULL_CHILD: case NULL_CHILD:
cprintf(cb, "null"); cprintf(cb, "null");
@ -323,9 +325,10 @@ xml2json1_cbuf(cbuf *cb,
break; break;
case FIRST_ARRAY: case FIRST_ARRAY:
case SINGLE_ARRAY: case SINGLE_ARRAY:
cprintf(cb, "%*s\"%s\": ", cprintf(cb, "%*s\"", pretty?(level*JSON_INDENT):0, "");
pretty?(level*JSON_INDENT):0, "", if (xml_namespace(x))
xml_name(x)); cprintf(cb, "%s:", xml_namespace(x));
cprintf(cb, "%s\": ", xml_name(x));
level++; level++;
cprintf(cb, "[%s%*s", cprintf(cb, "[%s%*s",
pretty?"\n":"", pretty?"\n":"",

View file

@ -587,7 +587,7 @@ yang_next(yang_node *y,
yang_stmt *ys; yang_stmt *ys;
if (y->yn_keyword == Y_SPEC) if (y->yn_keyword == Y_SPEC)
ys = yang_find_topnode((yang_spec*)y, name, 0); ys = yang_find_topnode((yang_spec*)y, name, YC_DATANODE);
else else
ys = yang_find_datanode(y, name); ys = yang_find_datanode(y, name);
if (ys == NULL) if (ys == NULL)
@ -1313,7 +1313,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, 0); /* still NULL for config */ y = yang_find_topnode(yspec, name, YC_DATANODE); /* still NULL for config */
#endif #endif
if (y) if (y)
xml_spec_set(x, y); xml_spec_set(x, y);
@ -1367,7 +1367,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, 0)) == NULL){ if ((y = yang_find_topnode(yspec, name, YC_DATANODE)) == 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;
} }
@ -1448,7 +1448,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[in] nodeclass Set to schema nodes, data nodes, etc
* @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
@ -1458,7 +1458,7 @@ api_path2xml_vec(char **vec,
int nvec, int nvec,
cxobj *x0, cxobj *x0,
yang_node *y0, yang_node *y0,
int schemanode, yang_class nodeclass,
cxobj **xpathp, cxobj **xpathp,
yang_node **ypathp) yang_node **ypathp)
{ {
@ -1500,10 +1500,10 @@ api_path2xml_vec(char **vec,
name = local; name = local;
} }
if (y0->yn_keyword == Y_SPEC){ /* top-node */ if (y0->yn_keyword == Y_SPEC){ /* top-node */
y = yang_find_topnode((yang_spec*)y0, name, schemanode); y = yang_find_topnode((yang_spec*)y0, name, nodeclass);
} }
else { else {
y = schemanode?yang_find_schemanode((yang_node*)y0, name): y = (nodeclass==YC_SCHEMANODE)?yang_find_schemanode((yang_node*)y0, name):
yang_find_datanode((yang_node*)y0, name); yang_find_datanode((yang_node*)y0, name);
} }
if (y == NULL){ if (y == NULL){
@ -1572,7 +1572,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, nodeclass,
xpathp, ypathp) < 0) xpathp, ypathp) < 0)
goto done; goto done;
retval = 0; retval = 0;
@ -1588,7 +1588,7 @@ api_path2xml_vec(char **vec,
* @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] yspec Yang spec
* @param[in,out] xtop Incoming XML tree * @param[in,out] xtop Incoming XML tree
* @param[in] schemanode If set use schema nodes otherwise data nodes. * @param[in] nodeclass Set to schema nodes, data nodes, etc
* @param[out] xbotp Resulting xml tree (end of xpath) * @param[out] xbotp Resulting xml tree (end of xpath)
* @param[out] ybotp Yang spec matching xbotp * @param[out] ybotp Yang spec matching xbotp
* @example * @example
@ -1605,7 +1605,7 @@ int
api_path2xml(char *api_path, api_path2xml(char *api_path,
yang_spec *yspec, yang_spec *yspec,
cxobj *xtop, cxobj *xtop,
int schemanode, yang_class nodeclass,
cxobj **xbotp, cxobj **xbotp,
yang_node **ybotp) yang_node **ybotp)
{ {
@ -1628,7 +1628,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,
xtop, (yang_node*)yspec, schemanode, xtop, (yang_node*)yspec, nodeclass,
xbotp, ybotp) < 0) xbotp, ybotp) < 0)
goto done; goto done;
retval = 0; retval = 0;
@ -1736,7 +1736,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, 0)) == NULL){ if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec"); clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done; goto done;
} }

View file

@ -93,7 +93,7 @@ xml_child_spec(char *name,
if (xp && (yparent = xml_spec(xp)) != NULL) if (xp && (yparent = xml_spec(xp)) != NULL)
y = yang_find_datanode((yang_node*)yparent, name); y = yang_find_datanode((yang_node*)yparent, name);
else if (yspec) else if (yspec)
y = yang_find_topnode(yspec, name, 0); /* still NULL for config */ y = yang_find_topnode(yspec, name, YC_DATANODE); /* still NULL for config */
else else
y = NULL; y = NULL;
*yresult = y; *yresult = y;

View file

@ -402,6 +402,94 @@ yang_find(yang_node *yn,
} }
return match ? ys : NULL; return match ? ys : NULL;
} }
#ifdef NOTYET
/*! Prototype more generic than yang_find_datanode and yang_find_schemanode
*/
yang_stmt *
yang_find_class(yang_node *yn,
char *argument,
yang_class class)
{
yang_stmt *ys = NULL;
yang_stmt *yc = NULL;
yang_stmt *ysmatch = NULL;
int i, j;
int ok;
for (i=0; i<yn->yn_len; i++){
ys = yn->yn_stmt[i];
switch(class){
case YC_NONE:
ok = 1;
break;
case YC_DATANODE:
ok = yang_datanode(ys);
break;
case YC_DATADEFINITION:
ok = yang_datadefinition(ys);
break;
case YC_SCHEMANODE:
ok = yang_schemanode(ys);
break;
}
if (!ok)
continue;
switch(class){
case YC_NONE:
if (argument == NULL)
ysmatch = ys;
else
if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0)
ysmatch = ys;
if (ysmatch)
goto match;
break;
case YC_DATANODE:
case YC_DATADEFINITION:
if (argument == NULL)
ysmatch = ys;
else
if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0)
ysmatch = ys;
if (ysmatch)
goto match;
break;
case YC_SCHEMANODE:
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_class((yang_node*)yc, argument, class);
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 (argument == NULL)
ysmatch = ys;
else
if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0)
ysmatch = ys;
if (ysmatch)
goto match;
}
break;
} /* switch */
} /* for */
match:
return ysmatch;
}
#endif /* NOTYET */
/*! Find child data node with matching argument (container, leaf, etc) /*! Find child data node with matching argument (container, leaf, etc)
* *
@ -455,6 +543,8 @@ yang_find_datanode(yang_node *yn,
} }
/*! Find child schema node with matching argument (container, leaf, etc) /*! Find child schema node with matching argument (container, leaf, etc)
* @param[in] yn Yang node, current context node.
* @param[in] argument if NULL, match any(first) argument.
* @note XXX unify code with yang_find_datanode? * @note XXX unify code with yang_find_datanode?
* @see yang_find_datanode * @see yang_find_datanode
*/ */
@ -505,7 +595,7 @@ yang_find_schemanode(yang_node *yn,
/*! Find first matching data node in all (sub)modules in a yang spec /*! 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] argument 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 * @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
@ -514,8 +604,8 @@ yang_find_schemanode(yang_node *yn,
*/ */
yang_stmt * yang_stmt *
yang_find_topnode(yang_spec *ysp, yang_find_topnode(yang_spec *ysp,
char *name, char *argument,
int schemanode) yang_class class)
{ {
yang_stmt *ys = NULL; yang_stmt *ys = NULL;
yang_stmt *yc = NULL; yang_stmt *yc = NULL;
@ -523,13 +613,22 @@ 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){ switch (class){
if ((yc = yang_find_schemanode((yang_node*)ys, name)) != NULL) case YC_NONE:
if ((yc = yang_find((yang_node*)ys, 0, argument)) != NULL)
return yc; return yc;
break;
case YC_DATANODE:
if ((yc = yang_find_datanode((yang_node*)ys, argument)) != NULL)
return yc;
break;
case YC_SCHEMANODE:
if ((yc = yang_find_schemanode((yang_node*)ys, argument)) != NULL)
return yc;
break;
case YC_DATADEFINITION:
break; /* nyi */
} }
else
if ((yc = yang_find_datanode((yang_node*)ys, name)) != NULL)
return yc;
} }
return NULL; return NULL;
} }
@ -1929,7 +2028,7 @@ yang_abs_schema_nodeid(yang_spec *yspec,
} }
if (ymod == NULL){ /* Try with topnode */ if (ymod == NULL){ /* Try with topnode */
yang_stmt *ys; yang_stmt *ys;
if ((ys = yang_find_topnode(yspec, id, 1)) == NULL){ if ((ys = yang_find_topnode(yspec, id, YC_SCHEMANODE)) == NULL){
clicon_err(OE_YANG, 0, "Module with id:%s:%s not found", prefix,id); clicon_err(OE_YANG, 0, "Module with id:%s:%s not found", prefix,id);
goto done; goto done;
} }

View file

@ -78,22 +78,49 @@ sleep 1
new "restconf tests" new "restconf tests"
new "restconf options" new "restconf root discovery. RFC 8040 3.1 (xml+xrd)"
expectfn "curl -i -sS -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE"
new "restconf head"
expectfn "curl -s -I http://localhost/restconf/data" "HTTP/1.1 200 OK"
#Content-Type: application/yang-data+json"
new "restconf root discovery"
expectfn "curl -s -X GET http://localhost/.well-known/host-meta" "<Link rel='restconf' href='/restconf'/>" expectfn "curl -s -X GET http://localhost/.well-known/host-meta" "<Link rel='restconf' href='/restconf'/>"
new "restconf get restconf json" new "restconf get restconf resource. RFC 8040 3.3 (json)"
expectfn "curl -sG http://localhost/restconf" '{"data": null,"operations": null,"yang-library-version": "2016-06-21"}}' expectfn "curl -sG http://localhost/restconf" '{"data": null,"operations": null,"yang-library-version": "2016-06-21"}}'
new "restconf get restconf/yang-library-version json" new "restconf get restconf resource. RFC 8040 3.3 (xml)"
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf)
expect="<restconf><data/><operations/><yang-library-version>2016-06-21</yang-library-version></restconf>"
match=`echo $ret | grep -EZo "$expect"`
if [ -z "$match" ]; then
err "$expect" "$ret"
fi
new "restconf get restconf/operations. RFC8040 3.3.2"
expectfn "curl -sG http://localhost/restconf/operations" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"rt:fib-route": null,"rt:route-count": null}}'
new "restconf get restconf/operations. RFC8040 3.3.2 (xml)"
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations)
expect="<operations><ex:empty/><ex:input/><ex:output/><rt:fib-route/><rt:route-count/></operations>"
match=`echo $ret | grep -EZo "$expect"`
if [ -z "$match" ]; then
err "$expect" "$ret"
fi
new "restconf get restconf/yang-library-version. RFC8040 3.3.3"
expectfn "curl -sG http://localhost/restconf/yang-library-version" '{"yang-library-version": "2016-06-21"}' expectfn "curl -sG http://localhost/restconf/yang-library-version" '{"yang-library-version": "2016-06-21"}'
new "restconf get restconf/yang-library-version. RFC8040 3.3.3 (xml)"
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/yang-library-version)
expect="<yang-library-version>2016-06-21</yang-library-version>"
match=`echo $ret | grep -EZo "$expect"`
if [ -z "$match" ]; then
err "$expect" "$ret"
fi
new "restconf options. RFC 8040 4.1"
expectfn "curl -i -s -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE"
new "restconf head. RFC 8040 4.2"
expectfn "curl -s -I http://localhost/restconf/data" "HTTP/1.1 200 OK"
#Content-Type: application/yang-data+json"
new "restconf empty rpc" new "restconf empty rpc"
expectfn 'curl -s -X POST -d {"input":{"name":""}} http://localhost/restconf/operations/ex:empty' '{"output": null}' expectfn 'curl -s -X POST -d {"input":{"name":""}} http://localhost/restconf/operations/ex:empty' '{"output": null}'
@ -101,6 +128,7 @@ new "restconf get empty config + state json"
expectfn "curl -sSG http://localhost/restconf/data" "{\"data\": $state}" expectfn "curl -sSG http://localhost/restconf/data" "{\"data\": $state}"
new "restconf get empty config + state xml" new "restconf get empty config + state xml"
# Cant get shell macros to work, inline matching from lib.sh
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data) ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data)
expect="<data><interfaces-state><interface><name>eth0</name><type>eth</type><if-index>42</if-index></interface></interfaces-state></data>" expect="<data><interfaces-state><interface><name>eth0</name><type>eth</type><if-index>42</if-index></interface></interfaces-state></data>"
match=`echo $ret | grep -EZo "$expect"` match=`echo $ret | grep -EZo "$expect"`

View file

@ -65,7 +65,6 @@ sleep 1
new "restconf tests" new "restconf tests"
new "restconf POST initial tree" new "restconf POST initial tree"
expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' "" expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' ""