* JSON encoding of YANG metadata according to RFC 7952
* XML -> JSON translation
This commit is contained in:
parent
76e59873c2
commit
6bf3112fe7
8 changed files with 145 additions and 29 deletions
|
|
@ -76,6 +76,8 @@ Users may have to change how they access the system
|
|||
* Restconf native HTTP/2:
|
||||
* Added option `CLICON_RESTCONF_HTTP2_PLAIN` for non-TLS http
|
||||
* Default disabled, set to true to enable HTTP/2 direct and switch/upgrade HTTP/1->HTTP/2
|
||||
* JSON encoding of YANG metadata according to RFC 7952
|
||||
* XML -> JSON translation
|
||||
* Restconf internal start: fail early if clixon_restconf binary is not found
|
||||
* If CLICON_BACKEND_RESTCONF_PROCESS is true
|
||||
* Added linenumbers to all YANG symbols for better debug and errors
|
||||
|
|
@ -1416,7 +1418,7 @@ Olof Hagsand
|
|||
* Clixon used to play the (already made) commit callbacks in reverse order
|
||||
* Many validation functions have changed error parameter from cbuf to xml tree.
|
||||
* XML trees are more flexible for utility tools
|
||||
* If you use these(mostly internal), you need to change the error function: `generic_validate, from_validate_common, xml_yang_validate_all_top, xml_yang_validate_all, xml_yang_validate_add, xml_yang_validate_rpc, xml_yang_validate_list_key_only`
|
||||
* If you use these(mostly internal), you need to change the error function: `generic_validate, from_validate_common, xml_yang_validate_all_top, xml_yang_validate_all, xml_yang_validate_add, xml_yang_validate_pprpc, xml_yang_validate_list_key_only`
|
||||
* Datastore cache and xmldb_get() changes:
|
||||
* You need to remove `msd` (last) parameter of `xmldb_get()`:
|
||||
* `xmldb_get(h, "running", "/", &xt, NULL)` --> `xmldb_get(h, "running", "/", &xt)`
|
||||
|
|
|
|||
|
|
@ -1662,10 +1662,10 @@ from_client_get_pageable_list(clicon_handle h,
|
|||
cprintf(cb, "%u", remaining);
|
||||
if (xml_value_set(xa, cbuf_get(cb)) < 0)
|
||||
goto done;
|
||||
if (xml_prefix_set(xa, "lpg") < 0)
|
||||
goto done;
|
||||
if (xmlns_set(x, "ycoll", "urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination") < 0)
|
||||
goto done;
|
||||
if (xml_prefix_set(xa, "ycoll") < 0)
|
||||
goto done;
|
||||
if (xmlns_set(x, "ycoll", "urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination") < 0)
|
||||
goto done;
|
||||
}
|
||||
/* Top level is data, so add 1 to depth if significant */
|
||||
if (clicon_xml2cbuf(cbret, x, 0, 0, depth>0?depth+1:depth) < 0)
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ yang_stmt *yang_find_module_by_namespace(yang_stmt *yspec, char *ns);
|
|||
yang_stmt *yang_find_module_by_namespace_revision(yang_stmt *yspec, const char *ns, const char *revision);
|
||||
yang_stmt *yang_find_module_by_name_revision(yang_stmt *yspec, const char *name, const char *revision);
|
||||
yang_stmt *yang_find_module_by_name(yang_stmt *yspec, char *name);
|
||||
int yang_metadata_annotation_check(cxobj *x, yang_stmt *ymod, int *ismeta);
|
||||
int yang_metadata_init(clicon_handle h);
|
||||
|
||||
#endif /* _CLIXON_YANG_MODULE_H_ */
|
||||
|
|
|
|||
|
|
@ -688,6 +688,39 @@ nullchild(cbuf *cb,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*!
|
||||
{
|
||||
"example-social:uint8-numbers": [17],
|
||||
"@example-social:uint8-numbers": [
|
||||
{
|
||||
"ietf-list-pagination:remaining": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
static int
|
||||
json_metadata_encoding(cbuf *cb,
|
||||
cxobj *x,
|
||||
int level,
|
||||
int pretty,
|
||||
char *modname,
|
||||
char *name,
|
||||
char *modname2,
|
||||
char *name2,
|
||||
char *val)
|
||||
{
|
||||
int retval = -1;
|
||||
|
||||
cprintf(cb, ",\"@%s:%s\": [", modname, name);
|
||||
cprintf(cb, "%*s", pretty?((level+1)*JSON_INDENT):0, "{");
|
||||
cprintf(cb, "\"%s:%s\": %s", modname2, name2, val);
|
||||
cprintf(cb, "%*s", pretty?((level+1)*JSON_INDENT):0, "}");
|
||||
cprintf(cb, "%*s", pretty?(level*JSON_INDENT):0, "]");
|
||||
retval = 0;
|
||||
// done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! 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
|
||||
|
|
@ -840,13 +873,8 @@ xml2json1_cbuf(cbuf *cb,
|
|||
commas = xml_child_nr_notype(x, CX_ATTR) - 1;
|
||||
for (i=0; i<xml_child_nr(x); i++){
|
||||
xc = xml_child_i(x, i);
|
||||
if (xml_type(xc) == CX_ATTR){
|
||||
#if 1 /* Work in progress, identify md:annotations */
|
||||
continue;
|
||||
#else
|
||||
if (xml_type(xc) == CX_ATTR)
|
||||
continue; /* XXX Only xmlns attributes mapped */
|
||||
#endif
|
||||
}
|
||||
xc_arraytype = array_eval(i?xml_child_i(x,i-1):NULL,
|
||||
xc,
|
||||
xml_child_i(x, i+1));
|
||||
|
|
@ -860,6 +888,35 @@ xml2json1_cbuf(cbuf *cb,
|
|||
--commas;
|
||||
}
|
||||
}
|
||||
#ifdef LIST_PAGINATION /* identify md:annotations as RFC 7952 Sec 5.2.1*/
|
||||
for (i=0; i<xml_child_nr(x); i++){
|
||||
xc = xml_child_i(x, i);
|
||||
if (xml_type(xc) == CX_ATTR){
|
||||
int ismeta = 0;
|
||||
char *namespace = NULL;
|
||||
yang_stmt *ymod;
|
||||
|
||||
if (xml2ns(xc, xml_prefix(xc), &namespace) < 0)
|
||||
goto done;
|
||||
if (namespace == NULL)
|
||||
continue;
|
||||
if ((ymod = yang_find_module_by_namespace(ys_spec(ys), namespace)) == NULL)
|
||||
continue;
|
||||
if (xml2ns(xc, xml_prefix(xc), &namespace) < 0)
|
||||
goto done;
|
||||
if (yang_metadata_annotation_check(xc, ymod, &ismeta) < 0)
|
||||
goto done;
|
||||
if (!ismeta)
|
||||
continue;
|
||||
if (json_metadata_encoding(cb, x, level, pretty,
|
||||
modname, xml_name(x),
|
||||
yang_argument_get(ymod),
|
||||
xml_name(xc),
|
||||
xml_value(xc)) < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
switch (arraytype){
|
||||
case BODY_ARRAY:
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -935,7 +935,7 @@ clicon_rpc_get_pageable_list(clicon_handle h,
|
|||
goto done;
|
||||
if ((cb = cbuf_new()) == NULL)
|
||||
goto done;
|
||||
cprintf(cb, "<rpc xmlns=\"%s\" ", NETCONF_BASE_NAMESPACE);
|
||||
cprintf(cb, "<rpc xmlns=\"%s\"", NETCONF_BASE_NAMESPACE);
|
||||
if ((username = clicon_username_get(h)) != NULL)
|
||||
cprintf(cb, " username=\"%s\"", username);
|
||||
cprintf(cb, " xmlns:%s=\"%s\"",
|
||||
|
|
|
|||
|
|
@ -879,7 +879,7 @@ yn_each(yang_stmt *yparent,
|
|||
/*! Find first child yang_stmt with matching keyword and argument
|
||||
*
|
||||
* @param[in] yn Yang node, current context node.
|
||||
* @param[in] keyword if 0 match any keyword
|
||||
* @param[in] keyword if 0 match any keyword. Actual type: enum rfc_6020
|
||||
* @param[in] argument String compare w argument. if NULL, match any.
|
||||
* @retval ys Yang statement, if any
|
||||
* This however means that if you actually want to match only a yang-stmt with
|
||||
|
|
|
|||
|
|
@ -690,6 +690,7 @@ yang_find_module_by_name(yang_stmt *yspec,
|
|||
* as described in Section 3.
|
||||
* Note this is called by the module using the extension md:annotate, not by
|
||||
* ietf-yang-metadata.yang
|
||||
* @see yang_metadata_annotation_check
|
||||
*/
|
||||
static int
|
||||
ietf_yang_metadata_extension_cb(clicon_handle h,
|
||||
|
|
@ -700,13 +701,15 @@ ietf_yang_metadata_extension_cb(clicon_handle h,
|
|||
char *extname;
|
||||
char *modname;
|
||||
yang_stmt *ymod;
|
||||
char *name;
|
||||
|
||||
ymod = ys_module(yext);
|
||||
modname = yang_argument_get(ymod);
|
||||
extname = yang_argument_get(yext);
|
||||
if (strcmp(modname, "ietf-yang-metadata") != 0 || strcmp(extname, "annotation") != 0)
|
||||
goto ok;
|
||||
clicon_debug(1, "%s Enabled extension:%s:%s", __FUNCTION__, modname, extname);
|
||||
name = cv_string_get(yang_cv_get(ys));
|
||||
clicon_debug(1, "%s Enabled extension:%s:%s:%s", __FUNCTION__, modname, extname, name);
|
||||
/* XXX Nothing yet - this should signal that xml attribute annotations are allowed
|
||||
* Possibly, add an "annotation" YANG node.
|
||||
*/
|
||||
|
|
@ -716,6 +719,48 @@ ietf_yang_metadata_extension_cb(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Check annotation extension
|
||||
*
|
||||
* @param[in] xa XML attribute
|
||||
* @param[in] ys YANG something
|
||||
* @param[out] ismeta Set to 1 if this is an annotation
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @see ietf_yang_metadata_extension_cb
|
||||
* XXX maybe a cache would be appropriate?
|
||||
* XXX: return type?
|
||||
*/
|
||||
int
|
||||
yang_metadata_annotation_check(cxobj *xa,
|
||||
yang_stmt *ymod,
|
||||
int *ismeta)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *yma = NULL;
|
||||
char *name;
|
||||
cg_var *cv;
|
||||
|
||||
/* Loop through annotations */
|
||||
while ((yma = yn_each(ymod, yma)) != NULL){
|
||||
/* Assume here md:annotation is written using canonical prefix */
|
||||
if (yang_keyword_get(yma) != Y_UNKNOWN)
|
||||
continue;
|
||||
if (strcmp(yang_argument_get(yma), "md:annotation") != 0)
|
||||
continue;
|
||||
if ((cv = yang_cv_get(yma)) != NULL &&
|
||||
(name = cv_string_get(cv)) != NULL){
|
||||
if (strcmp(name, xml_name(xa)) == 0){
|
||||
/* XXX: yang_find(yma,Y_TYPE,0) */
|
||||
*ismeta = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
retval = 0;
|
||||
// done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! In case ietf-yang-metadata is loaded by application, handle annotation extension
|
||||
* Consider moving fn
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Backlog items:
|
||||
# 1. "remaining" annotation RFC 7952
|
||||
# 2. pattern '.*[\n].*' { modifier invert-match;
|
||||
# XXX: augment Netconf GET instead, not RPC
|
||||
|
||||
# Magic line must be first in script (see README.md)
|
||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
|
@ -256,6 +257,27 @@ cat<<EOF > $fstate
|
|||
</audit-logs>
|
||||
EOF
|
||||
|
||||
# Run limitonly test with netconf, restconf+xml and restconf+json
|
||||
# Args:
|
||||
# 1. limit
|
||||
# 2. remaining
|
||||
# 3. list XXX remaining
|
||||
function testlimit()
|
||||
{
|
||||
limit=$1
|
||||
remaining=$2
|
||||
|
||||
new "limit=$limit NETCONF"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-pageable-list xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\"><datastore xmlns:ds=\"urn:ietf:params:xml:ns:yang:ietf-datastores\">ds:running</datastore><list-target xmlns:es=\"http://example.com/ns/example-social\">/es:members/es:member[es:member-id='alice']/es:favorites/es:uint8-numbers</list-target><limit>$limit</limit></get-pageable-list></rpc>]]>]]>" "<rpc-reply $DEFAULTNS><pageable-list xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\"><uint8-numbers xmlns=\"http://example.com/ns/example-social\" ycoll:remaining=\"$remaining\" xmlns:ycoll=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\">17</uint8-numbers></pageable-list></rpc-reply>]]>]]>$"
|
||||
|
||||
new "limit=$limit Parameter RESTCONF xml"
|
||||
expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-collection+xml" $RCPROTO://localhost/restconf/data/example-social:members/member=alice/favorites/uint8-numbers?limit=$limit)" 0 "HTTP/$HVER 200" "Content-Type: application/yang-collection+xml" "<pageable-list xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\"><uint8-numbers xmlns=\"http://example.com/ns/example-social\" ycoll:remaining=\"$remaining\" xmlns:ycoll=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\">17</uint8-numbers></pageable-list>"
|
||||
|
||||
# XXX [17]
|
||||
new "limit=$limit Parameter RESTCONF json"
|
||||
expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-collection+json" $RCPROTO://localhost/restconf/data/example-social:members/member=alice/favorites/uint8-numbers?limit=$limit)" 0 "HTTP/$HVER 200" "Content-Type: application/yang-collection+json" "{\"pageable-list\":{\"example-social:uint8-numbers\":17,\"@example-social:uint8-numbers\": \[{\"ietf-netconf-list-pagination:remaining\": $remaining}\]}}"
|
||||
}
|
||||
|
||||
new "test params: -f $cfg -s startup -- -sS $fstate"
|
||||
|
||||
if [ $BE -ne 0 ]; then
|
||||
|
|
@ -284,22 +306,11 @@ fi
|
|||
new "wait restconf"
|
||||
wait_restconf
|
||||
|
||||
new "A.3.1.1. 'limit=1' NETCONF"
|
||||
# XXX: augment GET instead, not RPC
|
||||
#expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-pageable-list xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\"><datastore xmlns:ds=\"urn:ietf:params:xml:ns:yang:ietf-datastores\">ds:running</datastore><list-target xmlns:es=\"http://example.com/ns/example-social\">/es:members/es:member[es:member-id='alice']/es:favorites/es:uint8-numbers</list-target><limit>1</limit></get-pageable-list></rpc>]]>]]>" "<rpc-reply $DEFAULTNS><pageable-list xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\"><uint8-numbers xmlns=\"http://example.com/ns/example-social\" ycoll:remaining=\"5\" xmlns:ycoll=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\">17</uint8-numbers></pageable-list></rpc-reply>]]>]]>$"
|
||||
new "A.3.1.1. limit=1"
|
||||
testlimit 1 5 "[17]"
|
||||
|
||||
new "A.3.1.1. 'limit' Parameter RESTCONF xml"
|
||||
#expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-collection+xml" $RCPROTO://localhost/restconf/data/example-social:members/member=alice/favorites/uint8-numbers?limit=1)" 0 "HTTP/$HVER 200" "Content-Type: application/yang-collection+xml" "<pageable-list xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\"><uint8-numbers xmlns=\"http://example.com/ns/example-social\" lpg:remaining=\"5\" xmlns:lpg=\"urn:ietf:params:xml:ns:yang:ietf-list-pagination\">17</uint8-numbers></pageable-list>"
|
||||
|
||||
new "A.3.1.1. 'limit' Parameter RESTCONF json"
|
||||
expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-collection+json" $RCPROTO://localhost/restconf/data/example-social:members/member=alice/favorites/uint8-numbers?limit=1)" 0 "HTTP/$HVER 200" "Content-Type: application/yang-collection+json" {'"pageable-list":{"example-social:uint8-numbers":17}}'
|
||||
exit
|
||||
|
||||
#new "A.3.1.1. 'limit' Parameter RESTCONF"
|
||||
#expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-collection+xml" $RCPROTO://localhost/restconf/data/ietf-netconf-list-pagination:get-pageable-list -d "<get-pageable-list xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\"><datastore xmlns:ds=\"urn:ietf:params:xml:ns:yang:ietf-datastores\">ds:running</datastore><list-target xmlns:es=\"http://example.com/ns/example-social\">/es:members/es:member[es:member-id='alice']/es:favorites/es:uint8-numbers</list-target><limit>1</limit></get-pageable-list>")" 0 "HTTP/$HVER 200" "Content-Type: application/yang-collection+xml" foo
|
||||
exit
|
||||
new "A.3.1.2. 'limit=2' NETCONF"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-pageable-list xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\"><datastore xmlns:ds=\"urn:ietf:params:xml:ns:yang:ietf-datastores\">ds:running</datastore><list-target xmlns:es=\"http://example.com/ns/example-social\">/es:members/es:member[es:member-id='alice']/es:favorites/es:uint8-numbers</list-target><limit>2</limit></get-pageable-list></rpc>]]>]]>" "<rpc-reply $DEFAULTNS><pageable-list xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\"><uint8-numbers xmlns=\"http://example.com/ns/example-social\" lpg:remaining=\"4\" xmlns:lpg=\"urn:ietf:params:xml:ns:yang:ietf-list-pagination\">17</uint8-numbers><uint8-numbers xmlns=\"http://example.com/ns/example-social\">13</uint8-numbers></pageable-list></rpc-reply>]]>]]>$"
|
||||
#new "A.3.1.2. limit=2"
|
||||
#testlimit 2 4 "[17 13]"
|
||||
|
||||
# CLI
|
||||
# XXX This relies on a very specific clispec command: need a more generic test
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue