RESTCONF over mountpoints, extended api_path2xml_mnt with mount-point check

This commit is contained in:
Olof hagsand 2025-02-24 11:55:48 +01:00
parent 9086264b89
commit b0cc1857c0
17 changed files with 299 additions and 213 deletions

View file

@ -930,3 +930,31 @@ restconf_socket_init(const char *netns0,
clixon_debug(CLIXON_DBG_RESTCONF, "retval:%d", retval); clixon_debug(CLIXON_DBG_RESTCONF, "retval:%d", retval);
return retval; return retval;
} }
/*! Callback used by api_path2xml when yang mountpoint is empty
*
* @param[in] h Clixon handle
* @param[in] x XML node
* @param[out] yp YANG
*/
int
restconf_apipath_mount_cb(clixon_handle h,
cxobj *x,
yang_stmt **yp)
{
int retval = -1;
cxobj *xyanglib = NULL;
int ret;
if (clixon_plugin_yang_mount_all(h, x, NULL, NULL, &xyanglib) < 0)
goto done;
if (xyanglib != NULL){
if ((ret = yang_schema_yanglib_mount_parse(h, x, xyanglib, yp)) < 0)
goto done;
}
retval = 0;
done:
if (xyanglib)
xml_free(xyanglib);
return retval;
}

View file

@ -101,6 +101,7 @@ int restconf_drop_privileges(clixon_handle h);
int restconf_authentication_cb(clixon_handle h, void *req, int pretty, restconf_media media_out); int restconf_authentication_cb(clixon_handle h, void *req, int pretty, restconf_media media_out);
int restconf_config_init(clixon_handle h, cxobj *xrestconf); int restconf_config_init(clixon_handle h, cxobj *xrestconf);
int restconf_socket_init(const char *netns0, const char *addrstr, const char *addrtype, uint16_t port, int backlog, int flags, int *ss); int restconf_socket_init(const char *netns0, const char *addrstr, const char *addrtype, uint16_t port, int backlog, int flags, int *ss);
int restconf_apipath_mount_cb(clixon_handle h, cxobj *x, yang_stmt **yp);
#endif /* _RESTCONF_LIB_H_ */ #endif /* _RESTCONF_LIB_H_ */

View file

@ -86,10 +86,10 @@
* @retval -1 Error * @retval -1 Error
* @code * @code
* curl -G http://localhost/restconf/data/interfaces/interface=eth0 * curl -G http://localhost/restconf/data/interfaces/interface=eth0
* @endcode * @endcode
* Minimal support: * Minimal support:
* 200 OK * 200 OK
* Allow: HEAD,GET,PUT,DELETE,OPTIONS * Allow: HEAD,GET,PUT,DELETE,OPTIONS
* @see RFC5789 PATCH Method for HTTP Section 3.2 * @see RFC5789 PATCH Method for HTTP Section 3.2
*/ */
int int
@ -113,7 +113,7 @@ api_data_options(clixon_handle h,
/*! Check matching keys /*! Check matching keys
* *
* Check that x1 and x2 are of type list/leaf-list and share the same key statements * Check that x1 and x2 are of type list/leaf-list and share the same key statements
* I.e that if x1=<list><key>b</key></list> then x2 = <list><key>b</key></list> as * I.e that if x1=<list><key>b</key></list> then x2 = <list><key>b</key></list> as
* well. Otherwise return -1. * well. Otherwise return -1.
* @param[in] y Yang statement, should be list or leaf-list * @param[in] y Yang statement, should be list or leaf-list
* @param[in] x1 First XML tree (eg data) * @param[in] x1 First XML tree (eg data)
@ -180,7 +180,7 @@ match_list_keys(yang_stmt *y,
return retval; return retval;
} }
/*! Common PUT plain PATCH method /*! Common PUT plain PATCH method
* *
* Code checks if object exists. * Code checks if object exists.
* PUT: If it does not, set op to create, otherwise replace * PUT: If it does not, set op to create, otherwise replace
@ -246,6 +246,25 @@ api_data_write(clixon_handle h,
/* strip /... from start */ /* strip /... from start */
for (i=0; i<pi; i++) for (i=0; i<pi; i++)
api_path = index(api_path+1, '/'); api_path = index(api_path+1, '/');
/* Create config top-of-tree */
if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
goto done;
/* Translate api_path to xml in the form of xtop/xbot */
xbot = xtop;
if (api_path){ /* If URI, otherwise top data/config object */
if ((ret = api_path2xml_mnt(api_path, yspec, xtop, YC_DATANODE, 1,
restconf_apipath_mount_cb, h, &xbot, &ybot, &xerr)) < 0)
goto done;
if (ret == 0){ /* validation failed */
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if (ybot){
if (ys_real_module(ybot, &ymodapi) < 0)
goto done;
}
}
if (api_path){ if (api_path){
/* Translate api-path to xpath: xpath (cbpath) and namespace context (nsc) */ /* Translate api-path to xpath: xpath (cbpath) and namespace context (nsc) */
if ((ret = api_path2xpath(api_path, yspec, &xpath, &nsc, &xerr)) < 0) if ((ret = api_path2xpath(api_path, yspec, &xpath, &nsc, &xerr)) < 0)
@ -260,24 +279,6 @@ api_data_write(clixon_handle h,
op = OP_MERGE; /* bad request if it does not exist */ op = OP_MERGE; /* bad request if it does not exist */
else else
op = OP_REPLACE; /* OP_CREATE if it does not exist */ op = OP_REPLACE; /* OP_CREATE if it does not exist */
/* Create config top-of-tree */
if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
goto done;
/* Translate api_path to xml in the form of xtop/xbot */
xbot = xtop;
if (api_path){ /* If URI, otherwise top data/config object */
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &ybot, &xerr)) < 0)
goto done;
if (ret == 0){ /* validation failed */
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if (ybot){
if (ys_real_module(ybot, &ymodapi) < 0)
goto done;
}
}
/* 4.4.1: The message-body MUST contain exactly one instance of the /* 4.4.1: The message-body MUST contain exactly one instance of the
* expected data resource. (tested again below) * expected data resource. (tested again below)
*/ */
@ -319,7 +320,7 @@ api_data_write(clixon_handle h,
else else
yb = YB_PARENT; yb = YB_PARENT;
/* Parse input data as json or xml into xml /* Parse input data as json or xml into xml
* Note that in POST (api_data_post) the new object is grafted on xbot, since it is a new * Note that in POST (api_data_post) the new object is grafted on xbot, since it is a new
* object. In that case all yang bindings can be made since xbot is available. * object. In that case all yang bindings can be made since xbot is available.
* Here the new object replaces xbot and is therefore more complicated to make when parsing. * Here the new object replaces xbot and is therefore more complicated to make when parsing.
@ -341,6 +342,7 @@ api_data_write(clixon_handle h,
} }
break; break;
case YANG_DATA_JSON: case YANG_DATA_JSON:
// XXX yspec is top-level, but data may be mounted yspec
if ((ret = clixon_json_parse_string(data, 1, yb, yspec, &xdata0, &xerr)) < 0){ if ((ret = clixon_json_parse_string(data, 1, yb, yspec, &xdata0, &xerr)) < 0){
if (netconf_malformed_message_xml(&xerr, clixon_err_reason()) < 0) if (netconf_malformed_message_xml(&xerr, clixon_err_reason()) < 0)
goto done; goto done;
@ -361,7 +363,7 @@ api_data_write(clixon_handle h,
} /* switch media_in */ } /* switch media_in */
/* The message-body MUST contain exactly one instance of the /* The message-body MUST contain exactly one instance of the
* expected data resource. * expected data resource.
*/ */
if (xml_child_nr_type(xdata0, CX_ELMNT) != 1){ if (xml_child_nr_type(xdata0, CX_ELMNT) != 1){
if (netconf_malformed_message_xml(&xerr, "The message-body MUST contain exactly one instance of the expected data resource") < 0) if (netconf_malformed_message_xml(&xerr, "The message-body MUST contain exactly one instance of the expected data resource") < 0)
@ -387,7 +389,7 @@ api_data_write(clixon_handle h,
} }
} }
/* Add operation create as attribute. If that fails with Conflict, then /* Add operation create as attribute. If that fails with Conflict, then
* try "replace" (see comment in function header) * try "replace" (see comment in function header)
*/ */
if (xml_add_attr(xdata, "operation", xml_operation2str(op), NETCONF_BASE_PREFIX, NULL) == NULL) if (xml_add_attr(xdata, "operation", xml_operation2str(op), NETCONF_BASE_PREFIX, NULL) == NULL)
@ -397,7 +399,7 @@ api_data_write(clixon_handle h,
CLIXON_LIB_PREFIX, CLIXON_LIB_NS) == NULL) CLIXON_LIB_PREFIX, CLIXON_LIB_NS) == NULL)
goto done; goto done;
/* Top-of tree, no api-path /* Top-of tree, no api-path
* Replace xparent with x, ie bottom of api-path with data * Replace xparent with x, ie bottom of api-path with data
*/ */
dname = xml_name(xdata); dname = xml_name(xdata);
if (api_path==NULL) { if (api_path==NULL) {
@ -438,7 +440,7 @@ api_data_write(clixon_handle h,
goto done; goto done;
goto ok; goto ok;
} }
/* If list or leaf-list, api-path keys must match data keys /* If list or leaf-list, api-path keys must match data keys
* There are two cases, either the object is the list element itself, * There are two cases, either the object is the list element itself,
* eg xpath:obj=a data:<obj><key>b</key></obj> * eg xpath:obj=a data:<obj><key>b</key></obj>
* or the object is the key element: * or the object is the key element:
@ -578,11 +580,11 @@ api_data_write(clixon_handle h,
if (xdata0) if (xdata0)
xml_free(xdata0); xml_free(xdata0);
if (cbx) if (cbx)
cbuf_free(cbx); cbuf_free(cbx);
return retval; return retval;
} /* api_data_write */ } /* api_data_write */
/*! Generic REST PUT method /*! Generic REST PUT method
* *
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] req Generic Www handle * @param[in] req Generic Www handle
@ -595,7 +597,7 @@ api_data_write(clixon_handle h,
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource * @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* @note restconf PUT is mapped to edit-config replace. * @note restconf PUT is mapped to edit-config replace.
* @see RFC8040 Sec 4.5 PUT * @see RFC8040 Sec 4.5 PUT
* @see api_data_post * @see api_data_post
* @example * @example
@ -605,7 +607,7 @@ api_data_write(clixon_handle h,
A request message-body MUST be present, representing the new data resource, or the server A request message-body MUST be present, representing the new data resource, or the server
MUST return a "400 Bad Request" status-line. MUST return a "400 Bad Request" status-line.
...if the PUT request creates a new resource, a "201 Created" status-line is returned. ...if the PUT request creates a new resource, a "201 Created" status-line is returned.
If an existing resource is modified, a "204 No Content" status-line is returned. If an existing resource is modified, a "204 No Content" status-line is returned.
* Netconf: <edit-config> (nc:operation="create/replace") * Netconf: <edit-config> (nc:operation="create/replace")
@ -637,7 +639,7 @@ api_data_put(clixon_handle h,
media_in, media_out, 0, ds); media_in, media_out, 0, ds);
} }
/*! Generic REST PATCH method for plain patch /*! Generic REST PATCH method for plain patch
* *
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] req Generic Www handle * @param[in] req Generic Www handle
@ -650,7 +652,7 @@ api_data_put(clixon_handle h,
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource * @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* Netconf: <edit-config> (nc:operation="merge") * Netconf: <edit-config> (nc:operation="merge")
* See RFC8040 Sec 4.6.1 * See RFC8040 Sec 4.6.1
* Plain patch can be used to create or update, but not delete, a child * Plain patch can be used to create or update, but not delete, a child
* resource within the target resource. * resource within the target resource.
@ -709,7 +711,7 @@ api_data_patch(clixon_handle h,
* See RFC 8040 Sec 4.7 * See RFC 8040 Sec 4.7
* 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
* Netconf: <edit-config> (nc:operation="delete") * Netconf: <edit-config> (nc:operation="delete")
*/ */
int int
api_data_delete(clixon_handle h, api_data_delete(clixon_handle h,
@ -749,7 +751,8 @@ api_data_delete(clixon_handle h,
goto done; goto done;
xbot = xtop; xbot = xtop;
if (api_path){ if (api_path){
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &y, &xerr)) < 0) if ((ret = api_path2xml_mnt(api_path, yspec, xtop, YC_DATANODE, 1,
restconf_apipath_mount_cb, h, &xbot, &y, &xerr)) < 0)
goto done; goto done;
if (ret == 0){ /* validation failed */ if (ret == 0){ /* validation failed */
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0) if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)

View file

@ -1,7 +1,7 @@
/* /*
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate) Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
@ -24,14 +24,14 @@
in which case the provisions of the GPL are applicable instead in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2, use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL. the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
* Restconf method implementation for operations get and data get and head * Restconf method implementation for operations get and data get and head
*/ */
@ -67,11 +67,12 @@
static int api_data_pagination(clixon_handle h, void *req, char *api_path, int pi, cvec *qvec, int pretty, restconf_media media_out); static int api_data_pagination(clixon_handle h, void *req, char *api_path, int pi, cvec *qvec, int pretty, restconf_media media_out);
/*! Generic GET (both HEAD and GET) /*! Generic GET (both HEAD and GET)
* According to restconf *
* According to restconf
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] req Generic Www handle * @param[in] req Generic Www 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] 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] pretty Set to 1 for pretty-printed xml/json output * @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media * @param[in] media_out Output media
@ -80,20 +81,20 @@ static int api_data_pagination(clixon_handle h, void *req, char *api_path, int p
* @retval -1 Error * @retval -1 Error
* @code * @code
* curl -X GET http://localhost/restconf/data/interfaces/interface=eth0 * curl -X GET http://localhost/restconf/data/interfaces/interface=eth0
* @endcode * @endcode
* See RFC8040 Sec 4.2 and 4.3 * See RFC8040 Sec 4.2 and 4.3
* XXX: cant find a way to use Accept request field to choose Content-Type * XXX: cant find a way to use Accept request field to choose Content-Type
* I would like to support both xml and json. * I would like to support both xml and json.
* 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
* "400 Bad Request" status-line MUST be returned by the server. * "400 Bad Request" status-line MUST be returned by the server.
* Netconf: <get-config>, <get> * Netconf: <get-config>, <get>
* @note there is an ad-hoc method to determine json pagination request instead of regular GET * @note there is an ad-hoc method to determine json pagination request instead of regular GET
*/ */
static int static int
@ -117,16 +118,15 @@ api_data_get2(clixon_handle h,
size_t xlen; size_t xlen;
int i; int i;
cxobj *x; cxobj *x;
int ret;
cvec *nsc = NULL; cvec *nsc = NULL;
char *attr; /* attribute value string */ char *attr; /* attribute value string */
netconf_content content = CONTENT_ALL; netconf_content content = CONTENT_ALL;
int32_t depth = -1; /* Nr of levels to print, -1 is all, 0 is none */ int32_t depth = -1; /* Nr of levels to print, -1 is all, 0 is none */
cxobj *xtop = NULL; cxobj *xtop = NULL;
cxobj *xbot = NULL;
yang_stmt *y = NULL; yang_stmt *y = NULL;
char *defaults = NULL; char *defaults = NULL;
cvec *nscd = NULL; cvec *nscd = NULL;
int ret;
clixon_debug(CLIXON_DBG_RESTCONF, ""); clixon_debug(CLIXON_DBG_RESTCONF, "");
if ((yspec = clicon_dbspec_yang(h)) == NULL){ if ((yspec = clicon_dbspec_yang(h)) == NULL){
@ -142,21 +142,28 @@ api_data_get2(clixon_handle h,
/* Translate api-path to xml, but to validate the api-path, note: strict=1 /* Translate api-path to xml, but to validate the api-path, note: strict=1
* xtop and xbot unnecessary for this function but needed by function * xtop and xbot unnecessary for this function but needed by function
*/ */
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &y, &xerr)) < 0) if ((ret = api_path2xml_mnt(api_path, yspec, xtop, YC_DATANODE, 1,
goto done; restconf_apipath_mount_cb, h, NULL, &y, &xerr)) < 0)
/* Translate api-path to xpath: xpath (cbpath) and namespace context (nsc) */
if (ret != 0 &&
(ret = api_path2xpath(api_path, yspec, &xpath, &nsc, &xerr)) < 0)
goto done; goto done;
if (ret == 0){ /* validation failed */ if (ret == 0){ /* validation failed */
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0) if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
/* Translate api-path to xpath: xpath (cbpath) and namespace context (nsc) */
if ((ret = api_path2xpath(api_path, yspec, &xpath, &nsc, &xerr)) < 0)
goto done;
if (ret == 0){ /* validation failed */
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
/* Ad-hoc method to determine json pagination request: /* Ad-hoc method to determine json pagination request:
* address list and one of "item/offset/.." is defined * address list and one of "item/offset/.." is defined
*/ */
if (!head && if (!head && y &&
(yang_keyword_get(y) == Y_LIST || yang_keyword_get(y) == Y_LEAF_LIST) && (yang_keyword_get(y) == Y_LIST || yang_keyword_get(y) == Y_LEAF_LIST) &&
(cvec_find(qvec, "where") || cvec_find(qvec, "sort-by") || (cvec_find(qvec, "where") || cvec_find(qvec, "sort-by") ||
cvec_find(qvec, "direction") || cvec_find(qvec, "offset") || cvec_find(qvec, "direction") || cvec_find(qvec, "offset") ||
@ -203,16 +210,14 @@ api_data_get2(clixon_handle h,
} }
clixon_debug(CLIXON_DBG_RESTCONF, "path:%s", xpath); clixon_debug(CLIXON_DBG_RESTCONF, "path:%s", xpath);
ret = clicon_rpc_get(h, xpath, nsc, content, depth, defaults, &xret); if ((ret = clicon_rpc_get(h, xpath, nsc, content, depth, defaults, &xret)) < 0){
if (ret < 0){
if (netconf_operation_failed_xml(&xerr, "protocol", clixon_err_reason()) < 0) if (netconf_operation_failed_xml(&xerr, "protocol", clixon_err_reason()) < 0)
goto done; goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0) if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
/* We get return via netconf which is complete tree from root /* We get return via netconf which is complete tree from root
* We need to cut that tree to only the object. * We need to cut that tree to only the object.
*/ */
#if 0 /* DEBUG */ #if 0 /* DEBUG */
@ -254,9 +259,9 @@ api_data_get2(clixon_handle h,
} }
/* Check if not exists */ /* Check if not exists */
if (xlen == 0){ if (xlen == 0){
/* 4.3: If a retrieval request for a data resource represents an /* 4.3: If a retrieval request for a data resource represents an
instance that does not exist, then an error response containing instance that does not exist, then an error response containing
a "404 Not Found" status-line MUST be returned by the server. a "404 Not Found" status-line MUST be returned by the server.
The error-tag value "invalid-value" is used in this case. */ The error-tag value "invalid-value" is used in this case. */
if (netconf_invalid_value_xml(&xerr, "application", "Instance does not exist") < 0) if (netconf_invalid_value_xml(&xerr, "application", "Instance does not exist") < 0)
goto done; goto done;
@ -323,13 +328,13 @@ api_data_get2(clixon_handle h,
return retval; return retval;
} }
/*! GET Collection /*! GET pagination
* *
* According to restconf collection draft. Lists, work in progress * According to restconf pagination draft. Lists, work in progress
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] req Generic Www handle * @param[in] req Generic Www 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] 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] pretty Set to 1 for pretty-printed xml/json output * @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media * @param[in] media_out Output media
@ -338,11 +343,7 @@ api_data_get2(clixon_handle h,
* @retval -1 Error * @retval -1 Error
* @code * @code
* curl -X GET http://localhost/restconf/data/interfaces * curl -X GET http://localhost/restconf/data/interfaces
* @endcode * @endcode
* A collection resource contains a set of data resources. It is used
* to represent a all instances or a subset of all instances in a YANG
* list or leaf-list.
* @see draft-ietf-netconf-restconf-collection-00.txt
*/ */
static int static int
api_data_pagination(clixon_handle h, api_data_pagination(clixon_handle h,
@ -391,14 +392,14 @@ api_data_pagination(clixon_handle h,
if (api_path){ if (api_path){
if ((xtop = xml_new("top", NULL, CX_ELMNT)) == NULL) if ((xtop = xml_new("top", NULL, CX_ELMNT)) == NULL)
goto done; goto done;
/* Translate api-path to xml, but to validate the api-path, note: strict=1 /* Translate api-path to xml, but to validate the api-path, note: strict=1
* xtop and xbot unnecessary for this function but needed by function * xtop and xbot unnecessary for this function but needed by function
* Set strict=0 to accept list uri:s with =keys syntax * Set strict=0 to accept list uri:s with =keys syntax
*/ */
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 0, &xbot, &y, &xerr)) < 0) if ((ret = api_path2xml_mnt(api_path, yspec, xtop, YC_DATANODE, 0,
restconf_apipath_mount_cb, h, &xbot, &y, &xerr)) < 0)
goto done; goto done;
/* Translate api-path to xpath: xpath (cbpath) and namespace context (nsc) /* Translate api-path to xpath: xpath (cbpath) and namespace context (nsc)
* XXX: xpath not used in collection?
*/ */
if (ret != 0 && if (ret != 0 &&
(ret = api_path2xpath(api_path, yspec, &xpath, &nsc, &xerr)) < 0) (ret = api_path2xpath(api_path, yspec, &xpath, &nsc, &xerr)) < 0)
@ -448,7 +449,7 @@ api_data_pagination(clixon_handle h,
clixon_err(OE_XML, EINVAL, "Invalid content attribute %d", content); clixon_err(OE_XML, EINVAL, "Invalid content attribute %d", content);
goto done; goto done;
} }
/* Clixon extensions and collection attributes */ /* Clixon extensions and pagination attributes */
/* Check for depth attribute */ /* Check for depth attribute */
if ((attr = cvec_find_str(qvec, "depth")) != NULL){ if ((attr = cvec_find_str(qvec, "depth")) != NULL){
clixon_debug(CLIXON_DBG_RESTCONF, "depth=%s", attr); clixon_debug(CLIXON_DBG_RESTCONF, "depth=%s", attr);
@ -502,7 +503,7 @@ api_data_pagination(clixon_handle h,
goto done; goto done;
goto ok; goto ok;
} }
/* We get return via netconf which is complete tree from root /* We get return via netconf which is complete tree from root
* We need to cut that tree to only the object. * We need to cut that tree to only the object.
*/ */
#if 0 /* DEBUG */ #if 0 /* DEBUG */
@ -588,14 +589,14 @@ api_data_pagination(clixon_handle h,
/*! REST HEAD method /*! REST HEAD method
* *
* 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.
* Relation to netconf: none * Relation to netconf: none
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] req Generic Www handle * @param[in] req Generic Www 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] 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] pretty Set to 1 for pretty-printed xml/json output * @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media * @param[in] media_out Output media
@ -618,12 +619,12 @@ api_data_head(clixon_handle h,
/*! REST GET method /*! REST GET method
* *
* According to restconf * According to restconf
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] req Generic Www handle * @param[in] req Generic Www 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] 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] pretty Set to 1 for pretty-printed xml/json output * @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media * @param[in] media_out Output media
@ -632,17 +633,17 @@ api_data_head(clixon_handle h,
* @retval -1 Error * @retval -1 Error
* @code * @code
* curl -G http://localhost/restconf/data/interfaces/interface=eth0 * curl -G http://localhost/restconf/data/interfaces/interface=eth0
* @endcode * @endcode
* 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
* 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
* "400 Bad Request" status-line MUST be returned by the server. * "400 Bad Request" status-line MUST be returned by the server.
* Netconf: <get-config>, <get> * Netconf: <get-config>, <get>
*/ */
int int
api_data_get(clixon_handle h, api_data_get(clixon_handle h,
@ -678,7 +679,7 @@ api_data_get(clixon_handle h,
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] req Generic Www handle * @param[in] req Generic Www handle
* @param[in] 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] 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] data Stream input data * @param[in] data Stream input data
* @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] pretty Set to 1 for pretty-printed xml/json output
@ -687,7 +688,7 @@ api_data_get(clixon_handle h,
* @retval -1 Error * @retval -1 Error
* @code * @code
* curl -G http://localhost/restconf/operations * curl -G http://localhost/restconf/operations
* @endcode * @endcode
* @see RFC8040 Sec 3.3.2: * @see RFC8040 Sec 3.3.2:
* This optional resource is a container that provides access to the * This optional resource is a container that provides access to the
* data-model-specific RPC operations supported by the server. The * data-model-specific RPC operations supported by the server. The

View file

@ -114,7 +114,7 @@ yang_patch_xml2json_modified_cbuf(cxobj *x_simple_patch)
char *json_simple_patch_tmp; char *json_simple_patch_tmp;
int brace_count = 0; int brace_count = 0;
size_t len; size_t len;
json_simple_patch = cbuf_new(); json_simple_patch = cbuf_new();
if (json_simple_patch == NULL) if (json_simple_patch == NULL)
return NULL; return NULL;
@ -150,7 +150,7 @@ yang_patch_xml2json_modified_cbuf(cxobj *x_simple_patch)
return json_simple_patch; return json_simple_patch;
} }
/*! yang_patch_strip_after_last_slash /*! yang_patch_strip_after_last_slash
* *
* Strip /... from end of val * Strip /... from end of val
* so that e.g. "/interface=eth2" becomes "/" * so that e.g. "/interface=eth2" becomes "/"
@ -166,7 +166,7 @@ yang_patch_strip_after_last_slash(char* val)
cbuf *cb; cbuf *cb;
cbuf *val_tmp; cbuf *val_tmp;
int idx; int idx;
cb = cbuf_new(); cb = cbuf_new();
val_tmp = cbuf_new(); val_tmp = cbuf_new();
cbuf_append_str(val_tmp, val); cbuf_append_str(val_tmp, val);
@ -212,7 +212,7 @@ yang_patch_do_replace(clixon_handle h,
restconf_media media_out, restconf_media media_out,
ietf_ds_t ds, ietf_ds_t ds,
cbuf *simple_patch_request_uri, cbuf *simple_patch_request_uri,
char *target_val, char *target_val,
int value_vec_len, int value_vec_len,
cxobj **value_vec, cxobj **value_vec,
cxobj *x_simple_patch cxobj *x_simple_patch
@ -223,7 +223,7 @@ yang_patch_do_replace(clixon_handle h,
cbuf *delete_req_uri = NULL; cbuf *delete_req_uri = NULL;
cbuf *post_req_uri = NULL; cbuf *post_req_uri = NULL;
cbuf *json_simple_patch = NULL; cbuf *json_simple_patch = NULL;
if ((delete_req_uri = cbuf_new()) == NULL){ if ((delete_req_uri = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new"); clixon_err(OE_UNIX, errno, "cbuf_new");
goto done; goto done;
@ -322,7 +322,7 @@ yang_patch_do_create(clixon_handle h,
int retval = -1; int retval = -1;
cxobj *value_vec_tmp = NULL; cxobj *value_vec_tmp = NULL;
cbuf *cb = NULL; cbuf *cb = NULL;
// Send the POST request // Send the POST request
if ((cb = cbuf_new()) == NULL){ if ((cb = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new"); clixon_err(OE_UNIX, errno, "cbuf_new");
@ -361,7 +361,7 @@ yang_patch_do_create(clixon_handle h,
* @param[in] value_vec pointer to the "value" array of an edit in YANG patch * @param[in] value_vec pointer to the "value" array of an edit in YANG patch
* @param[in] x_simple_patch pointer to XML containing module name, e.g. <ietf-interfaces:interface/> * @param[in] x_simple_patch pointer to XML containing module name, e.g. <ietf-interfaces:interface/>
* @param[in] where_val value in "where" field of edit in YANG patch * @param[in] where_val value in "where" field of edit in YANG patch
* @param[in] api_path full API path, e.g. "/restconf/data/example-jukebox:jukebox/playlist=Foo-One" * @param[in] api_path full API path, e.g. "/restconf/data/example-jukebox:jukebox/playlist=Foo-One"
* @param[in] point_val value in "point" field of edit in YANG patch * @param[in] point_val value in "point" field of edit in YANG patch
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
@ -388,7 +388,7 @@ yang_patch_do_insert(clixon_handle h,
cg_var *cv; cg_var *cv;
cbuf *point_str = NULL; cbuf *point_str = NULL;
cvec *qvec_tmp = NULL; cvec *qvec_tmp = NULL;
if ((point_str = cbuf_new()) == NULL){ if ((point_str = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new"); clixon_err(OE_UNIX, errno, "cbuf_new");
goto done; goto done;
@ -476,14 +476,13 @@ yang_patch_do_merge(clixon_handle h,
cxobj *value_vec_tmp = NULL; cxobj *value_vec_tmp = NULL;
cbuf *cb = NULL; cbuf *cb = NULL;
cbuf *json_simple_patch = NULL; cbuf *json_simple_patch = NULL;
if ((cb = cbuf_new()) == NULL){ if ((cb = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new"); clixon_err(OE_UNIX, errno, "cbuf_new");
goto done; goto done;
} }
if (key_xn != NULL) if (key_xn != NULL)
xml_addsub(x_simple_patch, key_xn); xml_addsub(x_simple_patch, key_xn);
// Loop through the XML, create JSON from each one, and submit a simple patch // Loop through the XML, create JSON from each one, and submit a simple patch
for (int k = 0; k < value_vec_len; k++) { for (int k = 0; k < value_vec_len; k++) {
if (value_vec[k] != NULL) { if (value_vec[k] != NULL) {
@ -618,7 +617,7 @@ yang_patch_do_edit(clixon_handle h,
size_t veclen = 0; size_t veclen = 0;
int ret; int ret;
cxobj *xerr = NULL; /* malloced must be freed */ cxobj *xerr = NULL; /* malloced must be freed */
cxobj *xtop; cxobj *xtop;
cxobj *xbot = NULL; cxobj *xbot = NULL;
yang_patch_op_t operation; yang_patch_op_t operation;
char *where_val = NULL; char *where_val = NULL;
@ -683,7 +682,7 @@ yang_patch_do_edit(clixon_handle h,
// Get key field // Get key field
/* Translate api_path to xml in the form of xtop/xbot */ /* Translate api_path to xml in the form of xtop/xbot */
xbot = xtop; xbot = xtop;
if ((ret = api_path2xml(cbuf_get(api_path_target), yspec, xtop, YC_DATANODE, 1, &xbot, &ybot, &xerr)) < 0) if ((ret = api_path2xml_mnt(h, cbuf_get(api_path_target), yspec, xtop, YC_DATANODE, 1, &xbot, &ybot, &xerr)) < 0)
goto done; goto done;
if (ret == 0){ /* validation failed */ if (ret == 0){ /* validation failed */
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0) if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)

View file

@ -64,13 +64,13 @@
#include "restconf_err.h" #include "restconf_err.h"
#include "restconf_methods_post.h" #include "restconf_methods_post.h"
/*! Print location header from /*! Print location header from
* *
* @param[in] req Generic Www handle * @param[in] req Generic Www handle
* @param[in] xobj If set (eg POST) add to api-path * @param[in] xobj If set (eg POST) add to api-path
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* $https on if connection operates in SSL mode, or an empty string otherwise * $https on if connection operates in SSL mode, or an empty string otherwise
* @note ports are ignored * @note ports are ignored
*/ */
static int static int
@ -117,7 +117,7 @@ http_location_header(clixon_handle h,
return retval; return retval;
} }
/*! Generic REST POST method /*! Generic REST POST method
* *
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] req Generic Www handle * @param[in] req Generic Www handle
@ -131,7 +131,7 @@ http_location_header(clixon_handle h,
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource * @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* restconf POST is mapped to edit-config create. * restconf POST is mapped to edit-config create.
* @see RFC8040 Sec 4.4.1 * @see RFC8040 Sec 4.4.1
POST: POST:
@ -203,7 +203,8 @@ api_data_post(clixon_handle h,
xbot = xtop; xbot = xtop;
if (api_path){ if (api_path){
/* Translate api-path to xml, side-effect: validate the api-path, note: strict=1 */ /* Translate api-path to xml, side-effect: validate the api-path, note: strict=1 */
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &ybot, &xerr)) < 0) if ((ret = api_path2xml_mnt(api_path, yspec, xtop, YC_DATANODE, 1,
restconf_apipath_mount_cb, h, &xbot, &ybot, &xerr)) < 0)
goto done; goto done;
if (ret == 0){ /* validation failed */ if (ret == 0){ /* validation failed */
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0) if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
@ -233,8 +234,8 @@ api_data_post(clixon_handle h,
yb = YB_MODULE; yb = YB_MODULE;
else else
yb = YB_PARENT; yb = YB_PARENT;
/* Parse input data as json or xml into xml /* Parse input data as json or xml into xml
* If xbot is top-level (api_path=null) it does not have a spec therefore look for * If xbot is top-level (api_path=null) it does not have a spec therefore look for
* top-level (yspec) otherwise assume parent (xbot) is populated. * top-level (yspec) otherwise assume parent (xbot) is populated.
*/ */
switch (media_in){ switch (media_in){
@ -273,7 +274,7 @@ api_data_post(clixon_handle h,
} /* switch media_in */ } /* switch media_in */
/* RFC 8040 4.4.1: The message-body MUST contain exactly one instance of the /* RFC 8040 4.4.1: The message-body MUST contain exactly one instance of the
* expected data resource. * expected data resource.
*/ */
clixon_debug(CLIXON_DBG_RESTCONF, "nrchildren0: %d", nrchildren0); clixon_debug(CLIXON_DBG_RESTCONF, "nrchildren0: %d", nrchildren0);
if (xml_child_nr_type(xbot, CX_ELMNT) - nrchildren0 != 1){ if (xml_child_nr_type(xbot, CX_ELMNT) - nrchildren0 != 1){
@ -315,7 +316,7 @@ api_data_post(clixon_handle h,
goto done; goto done;
goto ok; goto ok;
} }
/* If URI points out an object, then data's parent should be that object /* If URI points out an object, then data's parent should be that object
*/ */
if (ybot && yang_parent_get(ydata) != ybot){ if (ybot && yang_parent_get(ydata) != ybot){
if (netconf_malformed_message_xml(&xerr, "Data is not prefixed with matching namespace") < 0) if (netconf_malformed_message_xml(&xerr, "Data is not prefixed with matching namespace") < 0)
@ -399,12 +400,12 @@ api_data_post(clixon_handle h,
return retval; return retval;
} /* api_data_post */ } /* api_data_post */
/*! Handle input data to api_operations_post /*! Handle input data to api_operations_post
* *
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] req Generic Www handle * @param[in] req Generic Www handle
* @param[in] data Stream input data * @param[in] data Stream input data
* @param[in] yspec Yang top-level specification * @param[in] yspec Yang top-level specification
* @param[in] yrpc Yang rpc spec * @param[in] yrpc Yang rpc spec
* @param[in] xrpc XML pointer to rpc method * @param[in] xrpc XML pointer to rpc method
* @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] pretty Set to 1 for pretty-printed xml/json output
@ -449,7 +450,7 @@ api_operations_post_input(clixon_handle h,
media_in = restconf_content_type(h); media_in = restconf_content_type(h);
switch (media_in){ switch (media_in){
case YANG_DATA_XML: case YANG_DATA_XML:
/* XXX: Here data is on the form: <input xmlns="urn:example:clixon"/> and has no proper yang binding /* XXX: Here data is on the form: <input xmlns="urn:example:clixon"/> and has no proper yang binding
* support */ * support */
if ((ret = clixon_xml_parse_string(data, YB_NONE, yspec, &xdata, &xerr)) < 0){ if ((ret = clixon_xml_parse_string(data, YB_NONE, yspec, &xdata, &xerr)) < 0){
if (netconf_malformed_message_xml(&xerr, clixon_err_reason()) < 0) if (netconf_malformed_message_xml(&xerr, clixon_err_reason()) < 0)
@ -465,7 +466,7 @@ api_operations_post_input(clixon_handle h,
} }
break; break;
case YANG_DATA_JSON: case YANG_DATA_JSON:
/* XXX: Here data is on the form: {"clixon-example:input":null} and has no proper yang binding /* XXX: Here data is on the form: {"clixon-example:input":null} and has no proper yang binding
* support */ * support */
if ((ret = clixon_json_parse_string(data, 1, YB_NONE, yspec, &xdata, &xerr)) < 0){ if ((ret = clixon_json_parse_string(data, 1, YB_NONE, yspec, &xdata, &xerr)) < 0){
if (netconf_malformed_message_xml(&xerr, clixon_err_reason()) < 0) if (netconf_malformed_message_xml(&xerr, clixon_err_reason()) < 0)
@ -486,7 +487,7 @@ api_operations_post_input(clixon_handle h,
break; break;
} /* switch media_in */ } /* switch media_in */
xml_name_set(xdata, NETCONF_OUTPUT_DATA); xml_name_set(xdata, NETCONF_OUTPUT_DATA);
/* Here xdata is: /* Here xdata is:
* <data><input xmlns="urn:example:clixon">...</input></data> * <data><input xmlns="urn:example:clixon">...</input></data>
*/ */
#if 1 #if 1
@ -532,12 +533,12 @@ api_operations_post_input(clixon_handle h,
goto done; goto done;
} }
/*! Handle output data to api_operations_post /*! Handle output data to api_operations_post
* *
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] req Generic Www handle * @param[in] req Generic Www handle
* @param[in] xret XML reply messages from backend/handler * @param[in] xret XML reply messages from backend/handler
* @param[in] yspec Yang top-level specification * @param[in] yspec Yang top-level specification
* @param[in] youtput Yang rpc output specification * @param[in] youtput Yang rpc output specification
* @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media * @param[in] media_out Output media
@ -570,7 +571,7 @@ api_operations_post_output(clixon_handle h,
/* Validate that exactly only <rpc-reply> tag with exactly one element child */ /* Validate that exactly only <rpc-reply> tag with exactly one element child */
if ((xoutput = xml_child_i_type(xret, 0, CX_ELMNT)) == NULL || if ((xoutput = xml_child_i_type(xret, 0, CX_ELMNT)) == NULL ||
strcmp(xml_name(xoutput),"rpc-reply") != 0 strcmp(xml_name(xoutput),"rpc-reply") != 0
/* See https://github.com/clicon/clixon/issues/158 /* See https://github.com/clicon/clixon/issues/158
* This is an internal error, they should not be double but the error should not be detected * This is an internal error, they should not be double but the error should not be detected
* here, it should be detected in the backend plugin caller. * here, it should be detected in the backend plugin caller.
|| xml_child_nr_type(xrpc, CX_ELMNT) != 1 XXX backend can have multiple callbacks || xml_child_nr_type(xrpc, CX_ELMNT) != 1 XXX backend can have multiple callbacks
@ -594,7 +595,7 @@ api_operations_post_output(clixon_handle h,
if (xml_purge(xa) < 0) if (xml_purge(xa) < 0)
goto done; goto done;
/* Sanity check of outgoing XML /* Sanity check of outgoing XML
* For now, skip outgoing checks. * For now, skip outgoing checks.
* (1) Does not handle <ok/> properly * (1) Does not handle <ok/> properly
* (2) Uncertain how validation errors should be logged/handled * (2) Uncertain how validation errors should be logged/handled
@ -657,7 +658,7 @@ api_operations_post_output(clixon_handle h,
goto done; goto done;
} }
/*! REST operation POST method /*! REST operation POST method
* *
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] req Generic Www handle * @param[in] req Generic Www handle
@ -669,7 +670,7 @@ api_operations_post_output(clixon_handle h,
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* See RFC 8040 Sec 3.6 / 4.4.2 * See RFC 8040 Sec 3.6 / 4.4.2
* @note We map post to edit-config create. * @note We map post to edit-config create.
* POST {+restconf}/operations/<operation> * POST {+restconf}/operations/<operation>
* 1. Initialize * 1. Initialize
* 2. Get rpc module and name from uri (oppath) and find yang spec * 2. Get rpc module and name from uri (oppath) and find yang spec
@ -740,7 +741,7 @@ api_operations_post(clixon_handle h,
goto done; goto done;
goto ok; goto ok;
} }
/* 2. Get rpc module and name from uri (oppath) and find yang spec /* 2. Get rpc module and name from uri (oppath) and find yang spec
* POST {+restconf}/operations/<operation> * POST {+restconf}/operations/<operation>
* *
* The <operation> field identifies the module name and rpc identifier * The <operation> field identifies the module name and rpc identifier
@ -762,7 +763,7 @@ api_operations_post(clixon_handle h,
goto done; goto done;
goto ok; goto ok;
} }
/* 3. Build xml tree with user and rpc: /* 3. Build xml tree with user and rpc:
* <rpc username="foo"><myfn xmlns="uri"/> * <rpc username="foo"><myfn xmlns="uri"/>
*/ */
if ((username = clicon_username_get(h)) != NULL){ if ((username = clicon_username_get(h)) != NULL){
@ -777,14 +778,15 @@ api_operations_post(clixon_handle h,
if (xml_rootchild(xtop, 0, &xtop) < 0) if (xml_rootchild(xtop, 0, &xtop) < 0)
goto done; goto done;
xbot = xtop; xbot = xtop;
if ((ret = api_path2xml(oppath, yspec, xtop, YC_SCHEMANODE, 1, &xbot, &y, &xerr)) < 0) if ((ret = api_path2xml_mnt(oppath, yspec, xtop, YC_SCHEMANODE, 1,
restconf_apipath_mount_cb, h, &xbot, &y, &xerr)) < 0)
goto done; goto done;
if (ret == 0){ /* validation failed */ if (ret == 0){ /* validation failed */
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0) if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
/* Here xtop is: <rpc username="foo"><myfn xmlns="uri"/></rpc> /* Here xtop is: <rpc username="foo"><myfn xmlns="uri"/></rpc>
* xbot is <myfn xmlns="uri"/> * xbot is <myfn xmlns="uri"/>
* 4. Parse input data (arguments): * 4. Parse input data (arguments):
* JSON: {"example:input":{"x":0}} * JSON: {"example:input":{"x":0}}
@ -799,7 +801,7 @@ api_operations_post(clixon_handle h,
if (ret == 0) if (ret == 0)
goto ok; goto ok;
} }
/* Here xtop is: /* Here xtop is:
<rpc username="foo"><myfn xmlns="uri"><x>42</x></myfn></rpc> */ <rpc username="foo"><myfn xmlns="uri"><x>42</x></myfn></rpc> */
#if 1 #if 1
clixon_debug_xml(CLIXON_DBG_RESTCONF, xtop, ". Translate input args:"); clixon_debug_xml(CLIXON_DBG_RESTCONF, xtop, ". Translate input args:");
@ -829,8 +831,8 @@ api_operations_post(clixon_handle h,
* Note (1) xtop is <rpc><method> xbot is <method> * Note (1) xtop is <rpc><method> xbot is <method>
* (2) local handler wants <method> and backend wants <rpc><method> * (2) local handler wants <method> and backend wants <rpc><method>
*/ */
/* Look for local (client-side) restconf plugins. /* Look for local (client-side) restconf plugins.
* -1:Error, 0:OK local, 1:OK backend * -1:Error, 0:OK local, 1:OK backend
*/ */
if ((ret = rpc_callback_call(h, xbot, req, &nr, cbret)) < 0) if ((ret = rpc_callback_call(h, xbot, req, &nr, cbret)) < 0)
goto done; goto done;

View file

@ -373,11 +373,11 @@ example_restconf_start(clixon_handle h)
* @see RFC 8528 * @see RFC 8528
*/ */
int int
restconf_yang_mount(clixon_handle h, example_restconf_yang_mount(clixon_handle h,
cxobj *xt, cxobj *xt,
int *config, int *config,
validate_level *vl, validate_level *vl,
cxobj **yanglib) cxobj **yanglib)
{ {
int retval = -1; int retval = -1;
cbuf *cb = NULL; cbuf *cb = NULL;
@ -407,7 +407,6 @@ restconf_yang_mount(clixon_handle h,
if (xml_rootchild(*yanglib, 0, yanglib) < 0) if (xml_rootchild(*yanglib, 0, yanglib) < 0)
goto done; goto done;
} }
retval = 0; retval = 0;
done: done:
if (cb) if (cb)
@ -423,7 +422,7 @@ static clixon_plugin_api api = {
example_restconf_start,/* start */ example_restconf_start,/* start */
NULL, /* exit */ NULL, /* exit */
.ca_auth=example_restconf_credentials, /* auth */ .ca_auth=example_restconf_credentials, /* auth */
.ca_yang_mount=restconf_yang_mount, /* RFC 8528 schema mount */ .ca_yang_mount=example_restconf_yang_mount, /* RFC 8528 schema mount */
}; };
/*! Restconf plugin initialization /*! Restconf plugin initialization

View file

@ -72,6 +72,9 @@ typedef struct {
yang_stmt *cp_yang; /* Corresponding yang spec (after XML match - ie resolved) */ yang_stmt *cp_yang; /* Corresponding yang spec (after XML match - ie resolved) */
} clixon_path; } clixon_path;
/*! Callback if empty mount-point is encountered */
typedef int (api_path_mnt_cb_t)(clixon_handle h, cxobj *x, yang_stmt **yp);
/* /*
* Prototypes * Prototypes
*/ */
@ -84,6 +87,10 @@ int api_path2xpath(char *api_path, yang_stmt *yspec, char **xpath, cvec **nsc, c
int api_path2xml(char *api_path, yang_stmt *yspec, cxobj *xtop, int api_path2xml(char *api_path, yang_stmt *yspec, cxobj *xtop,
yang_class nodeclass, int strict, yang_class nodeclass, int strict,
cxobj **xpathp, yang_stmt **ypathp, cxobj **xerr); cxobj **xpathp, yang_stmt **ypathp, cxobj **xerr);
int api_path2xml_mnt(char *api_path, yang_stmt *yspec, cxobj *xtop,
yang_class nodeclass, int strict,
api_path_mnt_cb_t mnt_cb, void *arg,
cxobj **xpathp, yang_stmt **ypathp, cxobj **xerr);
int xml2api_path_1(cxobj *x, cbuf *cb); int xml2api_path_1(cxobj *x, cbuf *cb);
int clixon_xml_find_api_path(cxobj *xt, yang_stmt *yt, cxobj ***xvec, int *xlen, const char *format, int clixon_xml_find_api_path(cxobj *xt, yang_stmt *yt, cxobj ***xvec, int *xlen, const char *format,
...) __attribute__ ((format (printf, 5, 6)));; ...) __attribute__ ((format (printf, 5, 6)));;

View file

@ -99,7 +99,7 @@ enum array_element_type{
}; };
enum childtype{ enum childtype{
NULL_CHILD=0, /* eg <a/> no children. Translated to null if in NULL_CHILD=0, /* eg <a/> no children. Translated to null if in
* array or leaf terminal, and to {} if proper object, ie container. * array or leaf terminal, and to {} if proper object, ie container.
* anyxml/anydata? * anyxml/anydata?
*/ */
@ -107,7 +107,7 @@ enum childtype{
ANY_CHILD, /* eg <a><b/></a> or <a><b/><c/></a> */ ANY_CHILD, /* eg <a><b/></a> or <a><b/><c/></a> */
}; };
/*! x is element and has exactly one child which in turn has none /*! x is element and has exactly one child which in turn has none
* *
* remove attributes from x * remove attributes from x
* @see tleaf in clixon_xml_map.c * @see tleaf in clixon_xml_map.c
@ -182,14 +182,14 @@ arraytype2str(enum array_element_type lt)
} }
/*! Check typeof x in array /*! Check typeof x in array
* *
* Check if element is in an array, and if so, if it is in the start "[x,", in the middle: "[..,x,..]" * Check if element is in an array, and if so, if it is in the start "[x,", in the middle: "[..,x,..]"
* in the end: ",x]", or a single element: "[x]" * in the end: ",x]", or a single element: "[x]"
* Some complexity when x is in different namespaces * Some complexity when x is in different namespaces
* @param[in] xprev The previous element (if any) * @param[in] xprev The previous element (if any)
* @param[in] x The element itself * @param[in] x The element itself
* @param[in] xnext The next element (if any) * @param[in] xnext The next element (if any)
* @retval arraytype Type of array * @retval arraytype Type of array
*/ */
static enum array_element_type static enum array_element_type
array_eval(cxobj *xprev, array_eval(cxobj *xprev,
@ -296,7 +296,7 @@ json_str_escape_cdata(cbuf *cb,
* @param[in] x XML tree. Must be yang populated. * @param[in] x XML tree. Must be yang populated.
* @param[in] yspec Yang spec * @param[in] yspec Yang spec
* @param[out] xerr Reason for invalid tree returned as netconf err msg or NULL * @param[out] xerr Reason for invalid tree returned as netconf err msg or NULL
* @retval 1 OK * @retval 1 OK
* @retval 0 Invalid, wrt namespace. xerr set * @retval 0 Invalid, wrt namespace. xerr set
* @retval -1 Error * @retval -1 Error
* @see RFC7951 Sec 4 and 6.8 * @see RFC7951 Sec 4 and 6.8
@ -340,7 +340,7 @@ json2xml_decode_identityref(cxobj *x,
prefix, body, ns); prefix, body, ns);
if (!xml_nsctx_get_prefix(nsc, ns, &prefix2)){ if (!xml_nsctx_get_prefix(nsc, ns, &prefix2)){
/* (no) insert a xmlns:<prefix> statement /* (no) insert a xmlns:<prefix> statement
* Get yang prefix from import statement of my mod * Get yang prefix from import statement of my mod
* I am not sure this is correct * I am not sure this is correct
*/ */
if (yang_find_prefix_by_namespace(y, ns, &prefix2) < 0) if (yang_find_prefix_by_namespace(y, ns, &prefix2) < 0)
@ -396,11 +396,11 @@ json2xml_decode_identityref(cxobj *x,
* *
* Assume an xml tree where prefix:name have been split into "module":"name" * Assume an xml tree where prefix:name have been split into "module":"name"
* In other words, from JSON RFC7951 to XML namespace trees * In other words, from JSON RFC7951 to XML namespace trees
* *
* @param[in] x XML tree. Must be yang populated. After json parsing * @param[in] x XML tree. Must be yang populated. After json parsing
* @param[in] yspec Yang spec * @param[in] yspec Yang spec
* @param[out] xerr Reason for invalid tree returned as netconf err msg or NULL * @param[out] xerr Reason for invalid tree returned as netconf err msg or NULL
* @retval 1 OK * @retval 1 OK
* @retval 0 Invalid, wrt namespace. xerr set * @retval 0 Invalid, wrt namespace. xerr set
* @retval -1 Error * @retval -1 Error
* @see RFC7951 Sec 4 and 6.8 * @see RFC7951 Sec 4 and 6.8
@ -606,7 +606,7 @@ xml2json_encode_leafs(cxobj *xb,
} }
ok: ok:
/* write into original cb0 /* write into original cb0
* includign quoting and encoding * includign quoting and encoding
*/ */
if (quote){ if (quote){
cprintf(cb0, "\""); cprintf(cb0, "\"");
@ -625,8 +625,9 @@ xml2json_encode_leafs(cxobj *xb,
return retval; return retval;
} }
/* X has no XML child - no body. /*! Check if X has no XML child - no body.
* If x is a container, use {} instead of null *
* If x is a container, use {} instead of null
* if leaf or leaf-list then assume EMPTY type, then [null] * if leaf or leaf-list then assume EMPTY type, then [null]
* else null * else null
*/ */
@ -714,7 +715,7 @@ json_metadata_encoding(cbuf *cb,
/*! Encode XML attributes as JSON meta-data /*! Encode XML attributes as JSON meta-data
* *
* There are two methods for this: * There are two methods for this:
* 1) Registered meta-data according to RFC 7952 using md:annotate. * 1) Registered meta-data according to RFC 7952 using md:annotate.
* This is derived from a YANG module * This is derived from a YANG module
* Examples: ietf-origin:origin, ietf-list-pagination: remaining * Examples: ietf-origin:origin, ietf-list-pagination: remaining
* 2) Assigned, if someother mechanism, eg XSD is used * 2) Assigned, if someother mechanism, eg XSD is used
@ -781,7 +782,7 @@ xml2json_encode_attr(cxobj *xa,
return retval; return retval;
} }
/*! Do the actual work of translating XML to JSON /*! Do the actual work of translating XML to JSON
* *
* @param[out] cb Cligen text buffer containing json on exit * @param[out] cb Cligen text buffer containing json on exit
* @param[in] x XML tree structure containing XML to translate * @param[in] x XML tree structure containing XML to translate
@ -852,7 +853,7 @@ xml2json1_cbuf(cbuf *cb,
if (ys_real_module(ys, &ymod) < 0) if (ys_real_module(ys, &ymod) < 0)
goto done; goto done;
modname = yang_argument_get(ymod); modname = yang_argument_get(ymod);
/* Special case for ietf-netconf -> ietf-restconf translation /* Special case for ietf-netconf -> ietf-restconf translation
* A special case is for return data on the form {"data":...} * A special case is for return data on the form {"data":...}
* See also json_xmlns_translate() * See also json_xmlns_translate()
*/ */
@ -946,7 +947,7 @@ xml2json1_cbuf(cbuf *cb,
goto done; goto done;
} }
/* Check for typed sub-body if: /* Check for typed sub-body if:
* arraytype=* but child-type is BODY_CHILD * arraytype=* but child-type is BODY_CHILD
* This is code for writing <a>42</a> as "a":42 and not "a":"42" * This is code for writing <a>42</a> as "a":42 and not "a":"42"
*/ */
commas = xml_child_nr_notype(x, CX_ATTR) - 1; commas = xml_child_nr_notype(x, CX_ATTR) - 1;
@ -1050,9 +1051,8 @@ xml2json1_cbuf(cbuf *cb,
/*! Translate an XML tree to JSON in a CLIgen buffer /*! Translate an XML tree to JSON in a CLIgen buffer
* *
* XML-style namespace notation in tree, but RFC7951 in output assume yang * XML-style namespace notation in tree, but RFC7951 in output assume yang
* populated * populated
*
* @param[in,out] cb Cligen buffer to write to * @param[in,out] cb Cligen buffer to write to
* @param[in] x XML tree to translate from * @param[in] x XML tree to translate from
* @param[in] pretty Set if output is pretty-printed * @param[in] pretty Set if output is pretty-printed
@ -1121,9 +1121,8 @@ xml2json_cbuf1(cbuf *cb,
/*! Translate an XML tree to JSON in a CLIgen buffer skip top-level object /*! Translate an XML tree to JSON in a CLIgen buffer skip top-level object
* *
* XML-style namespace notation in tree, but RFC7951 in output assume yang * XML-style namespace notation in tree, but RFC7951 in output assume yang
* populated * populated
*
* @param[in,out] cb Cligen buffer to write to * @param[in,out] cb Cligen buffer to write to
* @param[in] xt Top-level xml object * @param[in] xt Top-level xml object
* @param[in] pretty Set if output is pretty-printed * @param[in] pretty Set if output is pretty-printed
@ -1178,7 +1177,7 @@ clixon_json2cbuf(cbuf *cb,
* @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
* @param[in] pretty Set if output is pretty-printed (2 for debug) * @param[in] pretty Set if output is pretty-printed (2 for debug)
* @param[in] skiptop 0: Include top object 1: Skip top-object, only children, * @param[in] skiptop 0: Include top object 1: Skip top-object, only children,
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* @note This only works if the vector is uniform, ie same object name. * @note This only works if the vector is uniform, ie same object name.
@ -1320,8 +1319,8 @@ json_print(FILE *f,
* @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
* @param[in] pretty Set if output is pretty-printed (2 for debug) * @param[in] pretty Set if output is pretty-printed (2 for debug)
* @param[in] fn File print function * @param[in] fn File print function
* @param[in] skiptop 0: Include top object 1: Skip top-object, only children, * @param[in] skiptop 0: Include top object 1: Skip top-object, only children,
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* @note This only works if the vector is uniform, ie same object name. * @note This only works if the vector is uniform, ie same object name.
@ -1357,11 +1356,11 @@ xml2json_vec(FILE *f,
* *
* Assume an xml tree where prefix:name have been split into "module":"name" * Assume an xml tree where prefix:name have been split into "module":"name"
* In other words, from JSON to XML namespace trees * In other words, from JSON to XML namespace trees
* *
* @param[in] yspec Yang spec * @param[in] yspec Yang spec
* @param[in,out] x XML tree. Translate it in-line * @param[in,out] x XML tree. Translate it in-line
* @param[out] xerr Reason for invalid tree returned as netconf err msg or NULL * @param[out] xerr Reason for invalid tree returned as netconf err msg or NULL
* @retval 1 OK * @retval 1 OK
* @retval 0 Invalid, wrt namespace. xerr set * @retval 0 Invalid, wrt namespace. xerr set
* @retval -1 Error * @retval -1 Error
* @note the opposite - xml2ns is made inline in xml2json1_cbuf * @note the opposite - xml2ns is made inline in xml2json1_cbuf
@ -1430,11 +1429,11 @@ json_xmlns_translate(yang_stmt *yspec,
* @param[in] yb How to bind yang to XML top-level when parsing (if rfc7951) * @param[in] yb How to bind yang to XML top-level when parsing (if rfc7951)
* @param[in] yspec Yang specification (if rfc 7951) * @param[in] yspec Yang specification (if rfc 7951)
* @param[out] xt XML top of tree typically w/o children on entry (but created) * @param[out] xt XML top of tree typically w/o children on entry (but created)
* @param[out] xerr Reason for invalid returned as netconf err msg * @param[out] xerr Reason for invalid returned as netconf err msg
* @retval 1 OK and valid * @retval 1 OK and valid
* @retval 0 Invalid (only if yang spec) * @retval 0 Invalid (only if yang spec)
* @retval -1 Error * @retval -1 Error
* *
* @see _xml_parse for XML variant * @see _xml_parse for XML variant
* @see http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf * @see http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
* @see RFC 7951 * @see RFC 7951
@ -1449,11 +1448,12 @@ _json_parse(char *str,
{ {
int retval = -1; int retval = -1;
clixon_json_yacc jy = {0,}; clixon_json_yacc jy = {0,};
int ret;
cxobj *x; cxobj *x;
cbuf *cberr = NULL; cbuf *cberr = NULL;
int i; int i;
int failed = 0; /* yang assignment */ int failed = 0; /* yang assignment */
yang_stmt *yspec1;
int ret;
if (clixon_debug_get() & CLIXON_DBG_DETAIL) if (clixon_debug_get() & CLIXON_DBG_DETAIL)
clixon_debug(CLIXON_DBG_PARSE|CLIXON_DBG_DETAIL, "%s", str); clixon_debug(CLIXON_DBG_PARSE|CLIXON_DBG_DETAIL, "%s", str);
@ -1473,11 +1473,15 @@ _json_parse(char *str,
clixon_err(OE_JSON, 0, "JSON parser error with no error code (should not happen)"); clixon_err(OE_JSON, 0, "JSON parser error with no error code (should not happen)");
goto done; goto done;
} }
if (xml_spec(xt))
yspec1 = ys_spec(xml_spec(xt));
else
yspec1 = yspec;
/* Traverse new objects */ /* Traverse new objects */
for (i = 0; i < jy.jy_xlen; i++) { for (i = 0; i < jy.jy_xlen; i++) {
x = jy.jy_xvec[i]; x = jy.jy_xvec[i];
/* RFC 7951 Section 4: A namespace-qualified member name MUST be used for all /* RFC 7951 Section 4: A namespace-qualified member name MUST be used for all
* members of a top-level JSON object * members of a top-level JSON object
*/ */
if (rfc7951 && xml_prefix(x) == NULL){ if (rfc7951 && xml_prefix(x) == NULL){
/* XXX: For top-level config file: */ /* XXX: For top-level config file: */
@ -1493,15 +1497,18 @@ _json_parse(char *str,
} }
} }
/* Names are split into name/prefix, but now add namespace info */ /* Names are split into name/prefix, but now add namespace info */
if ((ret = json_xmlns_translate(yspec, x, xerr)) < 0) // XXX yspec is top-level but x may be across mtpoint
if ((ret = json_xmlns_translate(yspec1, x, xerr)) < 0)
goto done; goto done;
if (ret == 0) if (ret == 0)
goto fail; goto fail;
/* Now assign yang stmts to each XML node /* Now assign yang stmts to each XML node
* XXX should be xml_bind_yang0_parent() sometimes. * XXX should be xml_bind_yang0_parent() sometimes.
*/ */
switch (yb){ switch (yb){
case YB_PARENT: case YB_NONE:
break;
case YB_MODULE:
if ((ret = xml_bind_yang0(NULL, x, yb, yspec, xerr)) < 0) if ((ret = xml_bind_yang0(NULL, x, yb, yspec, xerr)) < 0)
goto done; goto done;
if (ret == 0) if (ret == 0)
@ -1513,14 +1520,12 @@ _json_parse(char *str,
if (ret == 0) if (ret == 0)
failed++; failed++;
break; break;
case YB_MODULE: case YB_PARENT:
if ((ret = xml_bind_yang0(NULL, x, yb, yspec, xerr)) < 0) if ((ret = xml_bind_yang0(NULL, x, yb, yspec, xerr)) < 0)
goto done; goto done;
if (ret == 0) if (ret == 0)
failed++; failed++;
break; break;
case YB_NONE:
break;
case YB_RPC: case YB_RPC:
if ((ret = xml_bind_yang_rpc(NULL, x, yspec, xerr)) < 0) if ((ret = xml_bind_yang_rpc(NULL, x, yspec, xerr)) < 0)
goto done; goto done;
@ -1528,7 +1533,7 @@ _json_parse(char *str,
failed++; failed++;
break; break;
} }
/* Now find leafs with identityrefs (+transitive) and translate /* Now find leafs with identityrefs (+transitive) and translate
* prefixes in values to XML namespaces */ * prefixes in values to XML namespaces */
if ((ret = json2xml_decode(x, xerr)) < 0) if ((ret = json2xml_decode(x, xerr)) < 0)
goto done; goto done;
@ -1537,7 +1542,7 @@ _json_parse(char *str,
} }
if (failed) if (failed)
goto fail; goto fail;
/* Sort the complete tree after parsing. Sorting is not really meaningful if Yang /* Sort the complete tree after parsing. Sorting is not really meaningful if Yang
not bound */ not bound */
if (yb != YB_NONE) if (yb != YB_NONE)
if (xml_sort_recurse(xt) < 0) if (xml_sort_recurse(xt) < 0)
@ -1564,7 +1569,7 @@ _json_parse(char *str,
* @param[in] yb How to bind yang to XML top-level when parsing * @param[in] yb How to bind yang to XML top-level when parsing
* @param[in] yspec Yang specification, mandatory to make module->xmlns translation * @param[in] yspec Yang specification, mandatory to make module->xmlns translation
* @param[in,out] xt Top object, if not exists, on success it is created with name 'top' * @param[in,out] xt Top object, if not exists, on success it is created with name 'top'
* @param[out] xerr Reason for invalid returned as netconf err msg * @param[out] xerr Reason for invalid returned as netconf err msg
* @retval 1 OK and valid * @retval 1 OK and valid
* @retval 0 Invalid (only if yang spec) w xerr set * @retval 0 Invalid (only if yang spec) w xerr set
* @retval -1 Error * @retval -1 Error
@ -1599,24 +1604,24 @@ clixon_json_parse_string(char *str,
return _json_parse(str, rfc7951, yb, yspec, *xt, xerr); return _json_parse(str, rfc7951, yb, yspec, *xt, xerr);
} }
/*! Read a JSON definition from file and parse it into a parse-tree. /*! Read a JSON definition from file and parse it into a parse-tree.
* *
* File will be parsed as follows: * File will be parsed as follows:
* (1) parsed according to JSON; # Only this check if yspec is NULL * (1) parsed according to JSON; # Only this check if yspec is NULL
* (2) sanity checked wrt yang * (2) sanity checked wrt yang
* (3) namespaces check (using <ns>:<name> notation * (3) namespaces check (using <ns>:<name> notation
* (4) an xml parse tree will be returned * (4) an xml parse tree will be returned
* Note, only (1) and (4) will be done if yspec is NULL. * Note, only (1) and (4) will be done if yspec is NULL.
* Part of (3) is to split json names if they contain colon, * Part of (3) is to split json names if they contain colon,
* eg: name="a:b" -> prefix="a", name="b" * eg: name="a:b" -> prefix="a", name="b"
* But this is not done if yspec=NULL, and is not part of the JSON spec * But this is not done if yspec=NULL, and is not part of the JSON spec
* *
* @param[in] fp File descriptor to the JSON file (ASCII string) * @param[in] fp File descriptor to the JSON file (ASCII string)
* @param[in] rfc7951 Do sanity checks according to RFC 7951 JSON Encoding of Data Modeled with YANG * @param[in] rfc7951 Do sanity checks according to RFC 7951 JSON Encoding of Data Modeled with YANG
* @param[in] yb How to bind yang to XML top-level when parsing * @param[in] yb How to bind yang to XML top-level when parsing
* @param[in] yspec Yang specification, or NULL * @param[in] yspec Yang specification, or NULL
* @param[in,out] xt Pointer to (XML) parse tree. If empty, create. * @param[in,out] xt Pointer to (XML) parse tree. If empty, create.
* @param[out] xerr Reason for invalid returned as netconf err msg * @param[out] xerr Reason for invalid returned as netconf err msg
* @retval 1 OK and valid * @retval 1 OK and valid
* @retval 0 Invalid (only if yang spec) w xerr set * @retval 0 Invalid (only if yang spec) w xerr set
* @retval -1 Error * @retval -1 Error

View file

@ -1035,6 +1035,8 @@ api_path2xpath(char *api_path,
* @param[in] y0 Yang spec for x0 * @param[in] y0 Yang spec for x0
* @param[in] nodeclass Set to schema nodes, data nodes, etc * @param[in] nodeclass Set to schema nodes, data nodes, etc
* @param[in] strict Break if api-path is not "complete" otherwise ignore and continue * @param[in] strict Break if api-path is not "complete" otherwise ignore and continue
* @param[in] mnt_cb Callback to mount new yang if mount-point is empty
* @param[in] arg Argument to callback
* @param[out] xbotp Resulting xml tree * @param[out] xbotp Resulting xml tree
* @param[out] ybotp Yang spec matching xpathp * @param[out] ybotp Yang spec matching xpathp
* @param[out] xerr Netconf error message (if retval=0) * @param[out] xerr Netconf error message (if retval=0)
@ -1047,15 +1049,17 @@ api_path2xpath(char *api_path,
* @see api_path2xml * @see api_path2xml
*/ */
static int static int
api_path2xml_vec(char **vec, api_path2xml_vec(char **vec,
int nvec, int nvec,
cxobj *x0, cxobj *x0,
yang_stmt *y0, yang_stmt *y0,
yang_class nodeclass, yang_class nodeclass,
int strict, int strict,
cxobj **xbotp, api_path_mnt_cb_t mnt_cb,
yang_stmt **ybotp, void *arg,
cxobj **xerr) cxobj **xbotp,
yang_stmt **ybotp,
cxobj **xerr)
{ {
int retval = -1; int retval = -1;
char *nodeid; char *nodeid;
@ -1081,6 +1085,7 @@ api_path2xml_vec(char **vec,
char *xpath = NULL; char *xpath = NULL;
cvec *nsc = NULL; cvec *nsc = NULL;
int ymtpoint; /* y is mountpoint */ int ymtpoint; /* y is mountpoint */
int ret;
if ((nodeid = vec[0]) == NULL || strlen(nodeid)==0){ if ((nodeid = vec[0]) == NULL || strlen(nodeid)==0){
if (xbotp) if (xbotp)
@ -1112,9 +1117,12 @@ api_path2xml_vec(char **vec,
goto fail; goto fail;
} }
if (ymtpoint){ if (ymtpoint){
/* XXX: Ignore return value: if none are mounted, no change of yspec is made here */ if ((ret = yang_mount_get_yspec_any(y0, &y0)) < 0)
if (yang_mount_get_yspec_any(y0, &y0) < 0)
goto done; goto done;
if (ret == 0 && mnt_cb != NULL){
if (mnt_cb(arg, x0, &y0) < 0)
goto done;
}
} }
if ((ymod = yang_find_module_by_name(y0, prefix)) == NULL){ if ((ymod = yang_find_module_by_name(y0, prefix)) == NULL){
cprintf(cberr, "No such yang module prefix"); cprintf(cberr, "No such yang module prefix");
@ -1179,7 +1187,7 @@ api_path2xml_vec(char **vec,
} }
} }
else{ else{
/* Transform restval "a,b,c" to "a" "b" "c" (nvalvec=3) /* Transform restval "a,b,c" to "a" "b" "c" (nvalvec=3)
* Note that vnr can be < length of cvk, due to empty or unset values * Note that vnr can be < length of cvk, due to empty or unset values
* Note also that valvec entries are encoded * Note also that valvec entries are encoded
*/ */
@ -1269,7 +1277,7 @@ api_path2xml_vec(char **vec,
goto done; goto done;
} }
/* cf xml_bind_yang0_opt/xml_yang_mount_get */ /* cf xml_bind_yang0_opt/xml_yang_mount_get */
if (yang_mount_get(y, xpath, &y1) < 0) if (yang_mount_get(y, xpath, &y1) < 0)
goto done; goto done;
if (y1 != NULL) if (y1 != NULL)
y = y1; y = y1;
@ -1277,6 +1285,7 @@ api_path2xml_vec(char **vec,
if ((retval = api_path2xml_vec(vec+1, nvec-1, if ((retval = api_path2xml_vec(vec+1, nvec-1,
x, y, x, y,
nodeclass, strict, nodeclass, strict,
mnt_cb, arg,
xbotp, ybotp, xerr)) < 1) xbotp, ybotp, xerr)) < 1)
goto done; goto done;
ok: ok:
@ -1344,6 +1353,44 @@ api_path2xml(char *api_path,
cxobj **xbotp, cxobj **xbotp,
yang_stmt **ybotp, yang_stmt **ybotp,
cxobj **xerr) cxobj **xerr)
{
return api_path2xml_mnt(api_path, yspec, xtop, nodeclass, strict, NULL, NULL, xbotp, ybotp, xerr);
}
/*! Create xml tree from api-path as vector and as a side-effect mount yangs
*
* Specialized version of api_path2xml_vec
* @param[in] h Clixon handle
* @param[in] vec APIpath as char* vector
* @param[in] nvec Length of vec
* @param[in] x0 XML tree so far
* @param[in] y0 Yang spec for x0
* @param[in] nodeclass Set to schema nodes, data nodes, etc
* @param[in] strict Break if api-path is not "complete" otherwise ignore and continue
* @param[in] mnt_cb Callback if mount-point is empty
* @param[in] arg Argument to callback
* @param[out] xbotp Resulting xml tree
* @param[out] ybotp Yang spec matching xpathp
* @param[out] xerr Netconf error message (if retval=0)
* @retval 1 OK
* @retval 0 Invalid api_path or associated XML, netconf error
* @retval -1 Fatal error
*
* @note both retval -1 set clixon_err, retval 0 set xerr netconf xml
* @see api_path2xpath For api-path to xml xpath translation
* @see api_path2xml
*/
int
api_path2xml_mnt(char *api_path,
yang_stmt *yspec,
cxobj *xtop,
yang_class nodeclass,
int strict,
api_path_mnt_cb_t mnt_cb,
void *arg,
cxobj **xbotp,
yang_stmt **ybotp,
cxobj **xerr)
{ {
int retval = -1; int retval = -1;
char **vec = NULL; char **vec = NULL;
@ -1376,6 +1423,7 @@ api_path2xml(char *api_path,
nvec--; /* NULL-terminated */ nvec--; /* NULL-terminated */
if ((retval = api_path2xml_vec(vec+1, nvec, if ((retval = api_path2xml_vec(vec+1, nvec,
xtop, yspec, nodeclass, strict, xtop, yspec, nodeclass, strict,
mnt_cb, arg,
xbotp, ybotp, xerr)) < 1) xbotp, ybotp, xerr)) < 1)
goto done; goto done;
/* Fix namespace */ /* Fix namespace */
@ -1471,14 +1519,6 @@ xml2api_path_1(cxobj *x,
default: default:
break; break;
} }
#if 0
{ /* Just for testing */
cxobj *xc;
if ((xc = xml_child_i_type(x, 0, CX_ELMNT)) != NULL)
if (xml2api_path_1(xc, cb) < 0)
goto done;
}
#endif
ok: ok:
retval = 0; retval = 0;
done: done:

View file

@ -932,7 +932,7 @@ clixon_plugin_yang_mount_all(clixon_handle h,
return retval; return retval;
} }
/*! Call single backend statedata callback /*! Call single backend system-only callback
* *
* Create an xml tree (xret) for one callback only on the form: * Create an xml tree (xret) for one callback only on the form:
* <config>...</config>, * <config>...</config>,

View file

@ -890,7 +890,6 @@ _xml_parse(const char *str,
} }
xy.xy_xtop = xt; xy.xy_xtop = xt;
xy.xy_xparent = xt; xy.xy_xparent = xt;
xy.xy_yspec = yspec;
if (clixon_xml_parsel_init(&xy) < 0) if (clixon_xml_parsel_init(&xy) < 0)
goto done; goto done;
if (clixon_xml_parseparse(&xy) != 0) /* yacc returns 1 on error */ if (clixon_xml_parseparse(&xy) != 0) /* yacc returns 1 on error */

View file

@ -51,7 +51,6 @@ struct clixon_xml_parse_yacc {
cxobj *xy_xtop; /* cxobj top element (fixed) */ cxobj *xy_xtop; /* cxobj top element (fixed) */
cxobj *xy_xelement; /* cxobj active element (changes with parse context) */ cxobj *xy_xelement; /* cxobj active element (changes with parse context) */
cxobj *xy_xparent; /* cxobj parent element (changes with parse context) */ cxobj *xy_xparent; /* cxobj parent element (changes with parse context) */
yang_stmt *xy_yspec; /* If set, top-level yang-spec */
int xy_lex_state; /* lex return state */ int xy_lex_state; /* lex return state */
cxobj **xy_xvec; /* Vector of created top-level nodes (to know which are created) */ cxobj **xy_xvec; /* Vector of created top-level nodes (to know which are created) */
int xy_xlen; /* Length of xy_xvec */ int xy_xlen; /* Length of xy_xvec */

View file

@ -2445,9 +2445,9 @@ yang_spec_print(FILE *f,
} }
/*! Print yang top-level specs /*! Print yang top-level specs
* *a
* @param[in] f File to print to. * @param[in] f File to print to.
* @param[in] ymounts Yang mounts to print * @param[in] ymounts Yang mounts to print
*/ */
int int
yang_mounts_print(FILE *f, yang_mounts_print(FILE *f,

View file

@ -775,6 +775,7 @@ yang_schema_find_share(clixon_handle h,
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] xt XML tree node * @param[in] xt XML tree node
* @param[in] xyanglib XML yang-lib * @param[in] xyanglib XML yang-lib
* @param[out] yspecp Resulting mounted yang spec
* @retval 1 OK * @retval 1 OK
* @retval 0 No yanglib or problem when parsing yanglib * @retval 0 No yanglib or problem when parsing yanglib
* @retval -1 Error * @retval -1 Error

View file

@ -38,7 +38,6 @@ if [ -d ${TOP_SRCDIR}/yang/clixon ]; then
else else
cp /usr/local/share/clixon/$y $dir/ cp /usr/local/share/clixon/$y $dir/
fi fi
if [ "${WITH_RESTCONF}" = "native" ]; then if [ "${WITH_RESTCONF}" = "native" ]; then
# Create server certs # Create server certs
certdir=$dir/certs certdir=$dir/certs

View file

@ -241,6 +241,9 @@ expectpart "$($clixon_cli -1 -f $cfg show config xml -- -m clixon-mount0 -M urn:
new "restconf get config mntpoint" new "restconf get config mntpoint"
expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/data/clixon-example:top/mylist=x/root)" 0 "HTTP/$HVER 200" '<root xmlns="urn:example:clixon"><mount1 xmlns="urn:example:mount1"><mylist1><name1>x1</name1><options xmlns="urn:example:mount2"><option2>bar</option2></options></mylist1></mount1><yang-library xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library"><module-set><name>mylabel</name><module><name>clixon-mount0</name><namespace>urn:example:mount0</namespace></module></module-set></yang-library></root>' expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/data/clixon-example:top/mylist=x/root)" 0 "HTTP/$HVER 200" '<root xmlns="urn:example:clixon"><mount1 xmlns="urn:example:mount1"><mylist1><name1>x1</name1><options xmlns="urn:example:mount2"><option2>bar</option2></options></mylist1></mount1><yang-library xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library"><module-set><name>mylabel</name><module><name>clixon-mount0</name><namespace>urn:example:mount0</namespace></module></module-set></yang-library></root>'
new "restconf get across mountpoint"
expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/data/clixon-example:top/mylist=x/root/clixon-mount1:mount1)" 0 "HTTP/$HVER 200" '<mount1 xmlns="urn:example:mount1"><mylist1><name1>x1</name1><options xmlns="urn:example:mount2"><option2>bar</option2></options></mylist1></mount1>' --not-- '<root' '<yang-library'
if [ $RC -ne 0 ]; then if [ $RC -ne 0 ]; then
new "Kill restconf daemon" new "Kill restconf daemon"
stop_restconf stop_restconf