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

@ -72,6 +72,9 @@ typedef struct {
yang_stmt *cp_yang; /* Corresponding yang spec (after XML match - ie resolved) */
} 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
*/
@ -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,
yang_class nodeclass, int strict,
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 clixon_xml_find_api_path(cxobj *xt, yang_stmt *yt, cxobj ***xvec, int *xlen, const char *format,
...) __attribute__ ((format (printf, 5, 6)));;

View file

@ -99,7 +99,7 @@ enum array_element_type{
};
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.
* anyxml/anydata?
*/
@ -107,7 +107,7 @@ enum childtype{
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
* @see tleaf in clixon_xml_map.c
@ -182,14 +182,14 @@ arraytype2str(enum array_element_type lt)
}
/*! 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,..]"
* in the end: ",x]", or a single element: "[x]"
* Some complexity when x is in different namespaces
* @param[in] xprev The previous element (if any)
* @param[in] x The element itself
* @param[in] xnext The next element (if any)
* @retval arraytype Type of array
* @retval arraytype Type of array
*/
static enum array_element_type
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] yspec Yang spec
* @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 -1 Error
* @see RFC7951 Sec 4 and 6.8
@ -340,7 +340,7 @@ json2xml_decode_identityref(cxobj *x,
prefix, body, ns);
if (!xml_nsctx_get_prefix(nsc, ns, &prefix2)){
/* (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
*/
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"
* In other words, from JSON RFC7951 to XML namespace trees
*
*
* @param[in] x XML tree. Must be yang populated. After json parsing
* @param[in] yspec Yang spec
* @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 -1 Error
* @see RFC7951 Sec 4 and 6.8
@ -606,7 +606,7 @@ xml2json_encode_leafs(cxobj *xb,
}
ok:
/* write into original cb0
* includign quoting and encoding
* includign quoting and encoding
*/
if (quote){
cprintf(cb0, "\"");
@ -625,8 +625,9 @@ xml2json_encode_leafs(cxobj *xb,
return retval;
}
/* X has no XML child - no body.
* If x is a container, use {} instead of null
/*! Check if X has no XML child - no body.
*
* If x is a container, use {} instead of null
* if leaf or leaf-list then assume EMPTY type, then [null]
* else null
*/
@ -714,7 +715,7 @@ json_metadata_encoding(cbuf *cb,
/*! Encode XML attributes as JSON meta-data
*
* 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
* Examples: ietf-origin:origin, ietf-list-pagination: remaining
* 2) Assigned, if someother mechanism, eg XSD is used
@ -781,7 +782,7 @@ xml2json_encode_attr(cxobj *xa,
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[in] x XML tree structure containing XML to translate
@ -852,7 +853,7 @@ xml2json1_cbuf(cbuf *cb,
if (ys_real_module(ys, &ymod) < 0)
goto done;
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":...}
* See also json_xmlns_translate()
*/
@ -946,7 +947,7 @@ xml2json1_cbuf(cbuf *cb,
goto done;
}
/* 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"
*/
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
*
* XML-style namespace notation in tree, but RFC7951 in output assume yang
* populated
*
* XML-style namespace notation in tree, but RFC7951 in output assume yang
* populated
* @param[in,out] cb Cligen buffer to write to
* @param[in] x XML tree to translate from
* @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
*
* 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
*
* @param[in,out] cb Cligen buffer to write to
* @param[in] xt Top-level xml object
* @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] veclen Length of vector
* @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 -1 Error
* @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] veclen Length of vector
* @param[in] pretty Set if output is pretty-printed (2 for debug)
* @param[in] fn File print function
* @param[in] skiptop 0: Include top object 1: Skip top-object, only children,
* @param[in] fn File print function
* @param[in] skiptop 0: Include top object 1: Skip top-object, only children,
* @retval 0 OK
* @retval -1 Error
* @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"
* In other words, from JSON to XML namespace trees
*
*
* @param[in] yspec Yang spec
* @param[in,out] x XML tree. Translate it in-line
* @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 -1 Error
* @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] yspec Yang specification (if rfc 7951)
* @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 0 Invalid (only if yang spec)
* @retval -1 Error
*
*
* @see _xml_parse for XML variant
* @see http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
* @see RFC 7951
@ -1449,11 +1448,12 @@ _json_parse(char *str,
{
int retval = -1;
clixon_json_yacc jy = {0,};
int ret;
cxobj *x;
cbuf *cberr = NULL;
int i;
int failed = 0; /* yang assignment */
yang_stmt *yspec1;
int ret;
if (clixon_debug_get() & CLIXON_DBG_DETAIL)
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)");
goto done;
}
if (xml_spec(xt))
yspec1 = ys_spec(xml_spec(xt));
else
yspec1 = yspec;
/* Traverse new objects */
for (i = 0; i < jy.jy_xlen; i++) {
x = jy.jy_xvec[i];
/* RFC 7951 Section 4: A namespace-qualified member name MUST be used for all
* members of a top-level JSON object
/* RFC 7951 Section 4: A namespace-qualified member name MUST be used for all
* members of a top-level JSON object
*/
if (rfc7951 && xml_prefix(x) == NULL){
/* 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 */
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;
if (ret == 0)
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.
*/
switch (yb){
case YB_PARENT:
case YB_NONE:
break;
case YB_MODULE:
if ((ret = xml_bind_yang0(NULL, x, yb, yspec, xerr)) < 0)
goto done;
if (ret == 0)
@ -1513,14 +1520,12 @@ _json_parse(char *str,
if (ret == 0)
failed++;
break;
case YB_MODULE:
case YB_PARENT:
if ((ret = xml_bind_yang0(NULL, x, yb, yspec, xerr)) < 0)
goto done;
if (ret == 0)
failed++;
break;
case YB_NONE:
break;
case YB_RPC:
if ((ret = xml_bind_yang_rpc(NULL, x, yspec, xerr)) < 0)
goto done;
@ -1528,7 +1533,7 @@ _json_parse(char *str,
failed++;
break;
}
/* Now find leafs with identityrefs (+transitive) and translate
/* Now find leafs with identityrefs (+transitive) and translate
* prefixes in values to XML namespaces */
if ((ret = json2xml_decode(x, xerr)) < 0)
goto done;
@ -1537,7 +1542,7 @@ _json_parse(char *str,
}
if (failed)
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 */
if (yb != YB_NONE)
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] 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[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 0 Invalid (only if yang spec) w xerr set
* @retval -1 Error
@ -1599,24 +1604,24 @@ clixon_json_parse_string(char *str,
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:
* (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
* (4) an xml parse tree will be returned
* 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"
* 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] 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] yspec Yang specification, or NULL
* @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 0 Invalid (only if yang spec) w xerr set
* @retval -1 Error

View file

@ -1035,6 +1035,8 @@ api_path2xpath(char *api_path,
* @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 to mount new yang 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)
@ -1047,15 +1049,17 @@ api_path2xpath(char *api_path,
* @see api_path2xml
*/
static int
api_path2xml_vec(char **vec,
int nvec,
cxobj *x0,
yang_stmt *y0,
yang_class nodeclass,
int strict,
cxobj **xbotp,
yang_stmt **ybotp,
cxobj **xerr)
api_path2xml_vec(char **vec,
int nvec,
cxobj *x0,
yang_stmt *y0,
yang_class nodeclass,
int strict,
api_path_mnt_cb_t mnt_cb,
void *arg,
cxobj **xbotp,
yang_stmt **ybotp,
cxobj **xerr)
{
int retval = -1;
char *nodeid;
@ -1081,6 +1085,7 @@ api_path2xml_vec(char **vec,
char *xpath = NULL;
cvec *nsc = NULL;
int ymtpoint; /* y is mountpoint */
int ret;
if ((nodeid = vec[0]) == NULL || strlen(nodeid)==0){
if (xbotp)
@ -1112,9 +1117,12 @@ api_path2xml_vec(char **vec,
goto fail;
}
if (ymtpoint){
/* XXX: Ignore return value: if none are mounted, no change of yspec is made here */
if (yang_mount_get_yspec_any(y0, &y0) < 0)
if ((ret = yang_mount_get_yspec_any(y0, &y0)) < 0)
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){
cprintf(cberr, "No such yang module prefix");
@ -1179,7 +1187,7 @@ api_path2xml_vec(char **vec,
}
}
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 also that valvec entries are encoded
*/
@ -1269,7 +1277,7 @@ api_path2xml_vec(char **vec,
goto done;
}
/* 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;
if (y1 != NULL)
y = y1;
@ -1277,6 +1285,7 @@ api_path2xml_vec(char **vec,
if ((retval = api_path2xml_vec(vec+1, nvec-1,
x, y,
nodeclass, strict,
mnt_cb, arg,
xbotp, ybotp, xerr)) < 1)
goto done;
ok:
@ -1344,6 +1353,44 @@ api_path2xml(char *api_path,
cxobj **xbotp,
yang_stmt **ybotp,
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;
char **vec = NULL;
@ -1376,6 +1423,7 @@ api_path2xml(char *api_path,
nvec--; /* NULL-terminated */
if ((retval = api_path2xml_vec(vec+1, nvec,
xtop, yspec, nodeclass, strict,
mnt_cb, arg,
xbotp, ybotp, xerr)) < 1)
goto done;
/* Fix namespace */
@ -1471,14 +1519,6 @@ xml2api_path_1(cxobj *x,
default:
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:
retval = 0;
done:

View file

@ -932,7 +932,7 @@ clixon_plugin_yang_mount_all(clixon_handle h,
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:
* <config>...</config>,

View file

@ -890,7 +890,6 @@ _xml_parse(const char *str,
}
xy.xy_xtop = xt;
xy.xy_xparent = xt;
xy.xy_yspec = yspec;
if (clixon_xml_parsel_init(&xy) < 0)
goto done;
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_xelement; /* cxobj active 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 */
cxobj **xy_xvec; /* Vector of created top-level nodes (to know which are created) */
int xy_xlen; /* Length of xy_xvec */

View file

@ -2445,9 +2445,9 @@ yang_spec_print(FILE *f,
}
/*! Print yang top-level specs
*
*a
* @param[in] f File to print to.
* @param[in] ymounts Yang mounts to print
* @param[in] ymounts Yang mounts to print
*/
int
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] xt XML tree node
* @param[in] xyanglib XML yang-lib
* @param[out] yspecp Resulting mounted yang spec
* @retval 1 OK
* @retval 0 No yanglib or problem when parsing yanglib
* @retval -1 Error