Assigned meta-data for the ietf-netconf-with-defaults:default attribute for JSON (RFC8040 Sec 5.3.2)
This commit is contained in:
parent
a6f67755b3
commit
3bf8a7dca3
9 changed files with 140 additions and 106 deletions
18
CHANGELOG.md
18
CHANGELOG.md
|
|
@ -41,9 +41,21 @@ Expected: September 2022
|
||||||
|
|
||||||
### New features
|
### New features
|
||||||
|
|
||||||
* With-defaults RFC6243
|
* With-defaults capability
|
||||||
* see [Netconf With-defaults Capability](https://github.com/clicon/clixon/issues/262)
|
* Standards
|
||||||
* RESTCONF call home according to RFC 8071
|
* RFC 6243 "With-defaults Capability for NETCONF"
|
||||||
|
* RFC 8040 "RESTCONF Protocol": Section 4.8.9
|
||||||
|
* Features:
|
||||||
|
* `:with-defaults` capability
|
||||||
|
* `explicit` basic mode
|
||||||
|
* Full retrieval modes: `explicit`, `trim`, `report-all`, `report-all-tagged`.
|
||||||
|
* RESTCONF with-defaults query for XML and JSON
|
||||||
|
* Assigned meta-data for the `ietf-netconf-with-defaults:default` attribute for JSON (RFC8040 Sec 5.3.2)
|
||||||
|
* Added `withdefault` option to cli show commands
|
||||||
|
* See `CLISPEC changes of cli show functions` below
|
||||||
|
* See [Netconf With-defaults Capability](https://github.com/clicon/clixon/issues/262)
|
||||||
|
* RESTCONF call home
|
||||||
|
* Standard: RFC 8071 "NETCONF Call Home and RESTCONF Call Home"
|
||||||
* clixon-restconf.yang extended with callhome inspired by ietf-restconf-server.yang
|
* clixon-restconf.yang extended with callhome inspired by ietf-restconf-server.yang
|
||||||
* See e.g., draft-ietf-netconf-restconf-client-server-26.txt
|
* See e.g., draft-ietf-netconf-restconf-client-server-26.txt
|
||||||
* The `<socket>` list has been extended with a `call-home` presence container including:
|
* The `<socket>` list has been extended with a `call-home` presence container including:
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,6 @@ Clixon has a master branch continuously tested with CI, but releases are made ca
|
||||||
|
|
||||||
Clixon interaction is best done posting issues, pull requests, or joining the
|
Clixon interaction is best done posting issues, pull requests, or joining the
|
||||||
[slack channel](https://clixondev.slack.com).
|
[slack channel](https://clixondev.slack.com).
|
||||||
[Slack invite](https://join.slack.com/t/clixondev/shared_invite/zt-1da9fdr51-G~W5CES01_TRVdg5oXo43w)(updated 28/7 2022, valid 30d)
|
[Slack invite](https://join.slack.com/t/clixondev/shared_invite/zt-1fz8at20n-KGNmKD7KH2j2jIFGU_EhMQ)(updated 15/9 2022, valid 30d)
|
||||||
|
|
||||||
Clixon is sponsored by [Rubicon Communications LLC(Netgate)](https://www.netgate.com/)
|
Clixon is sponsored by [Rubicon Communications LLC(Netgate)](https://www.netgate.com/)
|
||||||
|
|
|
||||||
|
|
@ -503,13 +503,12 @@ restconf_insert_attributes(cxobj *xdata,
|
||||||
* @retval 0 OK, all callbacks executed OK
|
* @retval 0 OK, all callbacks executed OK
|
||||||
* @retval -1 Error in one callback
|
* @retval -1 Error in one callback
|
||||||
* @note This extension adds semantics to YANG according to RFC8040 as follows:
|
* @note This extension adds semantics to YANG according to RFC8040 as follows:
|
||||||
* - The list-stmt is not required to have a key-stmt defined.(NB!!)
|
* - The list-stmt is not required to have a key-stmt defined.(NB!!)
|
||||||
* - The if-feature-stmt is ignored if present.
|
* - The if-feature-stmt is ignored if present.
|
||||||
* - The config-stmt is ignored if present.
|
* - The config-stmt is ignored if present.
|
||||||
* - The available identity values for any 'identityref'
|
* - The available identity values for any 'identityref'
|
||||||
* leaf or leaf-list nodes are limited to the module
|
* leaf or leaf-list nodes are limited to the module containing this extension statement and
|
||||||
* containing this extension statement and the modules
|
* the modules imported into that module.
|
||||||
* imported into that module.
|
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
restconf_main_extension_cb(clicon_handle h,
|
restconf_main_extension_cb(clicon_handle h,
|
||||||
|
|
|
||||||
|
|
@ -315,7 +315,6 @@ main(int argc,
|
||||||
char *inline_config = NULL;
|
char *inline_config = NULL;
|
||||||
size_t sz;
|
size_t sz;
|
||||||
|
|
||||||
|
|
||||||
/* In the startup, logs to stderr & debug flag set later */
|
/* In the startup, logs to stderr & debug flag set later */
|
||||||
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
|
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -141,8 +141,8 @@ typedef int (plgexit_t)(clicon_handle); /* Plugin exit */
|
||||||
* @param[in] h Clixon handle
|
* @param[in] h Clixon handle
|
||||||
* @param[in] yext Yang node of extension
|
* @param[in] yext Yang node of extension
|
||||||
* @param[in] ys Yang node of (unknown) statement belonging to extension
|
* @param[in] ys Yang node of (unknown) statement belonging to extension
|
||||||
* @retval 0 OK, all callbacks executed OK
|
* @retval 0 OK, all callbacks executed OK
|
||||||
* @retval -1 Error in one callback
|
* @retval -1 Error in one callback
|
||||||
*/
|
*/
|
||||||
typedef int (plgextension_t)(clicon_handle h, yang_stmt *yext, yang_stmt *ys);
|
typedef int (plgextension_t)(clicon_handle h, yang_stmt *yext, yang_stmt *ys);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -697,48 +697,126 @@ nullchild(cbuf *cb,
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*! Encode json metadata
|
||||||
{
|
* This function could be more general, code based on two examples:
|
||||||
"example-social:uint8-numbers": [17],
|
* 1) ietf-list-pagination:remaining
|
||||||
"@example-social:uint8-numbers": [
|
* {
|
||||||
{
|
* "example-social:uint8-numbers": [17],
|
||||||
"ietf-list-pagination:remaining": 5
|
* "@example-social:uint8-numbers": [
|
||||||
}
|
* {
|
||||||
]
|
* "ietf-list-pagination:remaining": 5
|
||||||
}
|
* }
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
* 2) ietf-netconf-with-defaults:default:
|
||||||
|
* "@mtu" : {
|
||||||
|
* "ietf-netconf-with-defaults:default" : true
|
||||||
|
* },
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
json_metadata_encoding(cbuf *cb,
|
json_metadata_encoding(cbuf *cb,
|
||||||
cxobj *x,
|
cxobj *x,
|
||||||
int level,
|
int level,
|
||||||
int pretty,
|
int pretty,
|
||||||
char *modname,
|
char *prefix,
|
||||||
char *name,
|
char *name,
|
||||||
char *modname2,
|
char *modname2,
|
||||||
char *name2,
|
char *name2,
|
||||||
char *val)
|
char *val,
|
||||||
|
int list)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
cprintf(cb, ",\"@");
|
||||||
|
if (prefix)
|
||||||
cprintf(cb, ",\"@%s:%s\":[", modname, name);
|
cprintf(cb, "%s:", prefix);
|
||||||
|
cprintf(cb, "%s\":", name);
|
||||||
|
if (list)
|
||||||
|
cprintf(cb, "[");
|
||||||
cprintf(cb, "%*s", pretty?((level+1)*JSON_INDENT):0, "{");
|
cprintf(cb, "%*s", pretty?((level+1)*JSON_INDENT):0, "{");
|
||||||
cprintf(cb, "\"%s:%s\":%s", modname2, name2, val);
|
cprintf(cb, "\"%s:%s\":%s", modname2, name2, val);
|
||||||
cprintf(cb, "%*s", pretty?((level+1)*JSON_INDENT):0, "}");
|
cprintf(cb, "%*s", pretty?((level+1)*JSON_INDENT):0, "}");
|
||||||
cprintf(cb, "%*s", pretty?(level*JSON_INDENT):0, "]");
|
if (list)
|
||||||
|
cprintf(cb, "%*s", pretty?(level*JSON_INDENT):0, "]");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Encode XML attributes as JSON meta-data
|
||||||
|
*
|
||||||
|
* There are two methods for this:
|
||||||
|
* 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
|
||||||
|
* Example: ietf-restconf: default
|
||||||
|
* If neither matches, the attribute is skipped
|
||||||
|
* @param[in] xc XML attribute
|
||||||
|
* @param[in] xp XML node, parent of xa
|
||||||
|
* @param[in] yp Yang spec of xp
|
||||||
|
* @param[in] level Indentation level
|
||||||
|
* @param[in] level Indentation level
|
||||||
|
* @param[in] pretty Pretty-print output (2 means debug)
|
||||||
|
* @param[in] modname Name of yang module
|
||||||
|
* @param[in,out] metacb Encode into cbuf
|
||||||
|
* @see RFC7952
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
xml2json_encode_attr(cxobj *xa,
|
||||||
|
cxobj *xp,
|
||||||
|
yang_stmt *yp,
|
||||||
|
int level,
|
||||||
|
int pretty,
|
||||||
|
char *modname,
|
||||||
|
cbuf *metacb)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
int ismeta = 0;
|
||||||
|
char *namespace = NULL;
|
||||||
|
yang_stmt *ymod;
|
||||||
|
enum rfc_6020 ykeyw;
|
||||||
|
|
||||||
|
if (xml2ns(xa, xml_prefix(xa), &namespace) < 0)
|
||||||
|
goto done;
|
||||||
|
/* Check for (1) registered meta-data */
|
||||||
|
if (namespace != NULL && yp){
|
||||||
|
ykeyw = yang_keyword_get(yp);
|
||||||
|
if ((ymod = yang_find_module_by_namespace(ys_spec(yp), namespace)) != NULL){
|
||||||
|
if (yang_metadata_annotation_check(xa, ymod, &ismeta) < 0)
|
||||||
|
goto done;
|
||||||
|
if (ismeta)
|
||||||
|
if (json_metadata_encoding(metacb, xp, level, pretty,
|
||||||
|
modname, xml_name(xp),
|
||||||
|
yang_argument_get(ymod),
|
||||||
|
xml_name(xa),
|
||||||
|
xml_value(xa),
|
||||||
|
ykeyw == Y_LEAF_LIST || ykeyw == Y_LIST) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* Check for (2) assigned - hardcoded for now */
|
||||||
|
else if (strcmp(namespace, "urn:ietf:params:xml:ns:netconf:default:1.0") == 0 &&
|
||||||
|
strcmp(xml_name(xa), "default") == 0){
|
||||||
|
/* RFC 7952 / RFC 8040 defaults attribute */
|
||||||
|
if (json_metadata_encoding(metacb, xp, level, pretty,
|
||||||
|
modname, xml_name(xp),
|
||||||
|
"ietf-netconf-with-defaults",
|
||||||
|
xml_name(xa),
|
||||||
|
xml_value(xa),
|
||||||
|
ykeyw == Y_LEAF_LIST || ykeyw == Y_LIST) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
retval = 0;
|
retval = 0;
|
||||||
// done:
|
done:
|
||||||
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
|
||||||
* @param[in] yp Parent yang spec needed for body
|
|
||||||
* @param[in] arraytype Does x occur in a array (of its parent) and how?
|
* @param[in] arraytype Does x occur in a array (of its parent) and how?
|
||||||
* @param[in] level Indentation level
|
* @param[in] level Indentation level
|
||||||
* @param[in] pretty Pretty-print output (2 means debug)
|
* @param[in] pretty Pretty-print output (2 means debug)
|
||||||
* @param[in] flat Dont print NO_ARRAY object name (for _vec call)
|
* @param[in] flat Dont print NO_ARRAY object name (for _vec call)
|
||||||
* @param[in] bodystr Set if value is string, 0 otherwise. Only if body
|
* @param[in] modname0
|
||||||
|
* @param[out] metacbp Meta encoding of attribute
|
||||||
*
|
*
|
||||||
* @note Does not work with XML attributes
|
* @note Does not work with XML attributes
|
||||||
* The following matrix explains how the mapping is done.
|
* The following matrix explains how the mapping is done.
|
||||||
|
|
@ -776,7 +854,7 @@ xml2json1_cbuf(cbuf *cb,
|
||||||
int pretty,
|
int pretty,
|
||||||
int flat,
|
int flat,
|
||||||
char *modname0,
|
char *modname0,
|
||||||
cbuf **metacbp)
|
cbuf *metacbp)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
int i;
|
int i;
|
||||||
|
|
@ -883,6 +961,10 @@ xml2json1_cbuf(cbuf *cb,
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if ((metacbc = cbuf_new()) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||||
|
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"
|
||||||
|
|
@ -891,38 +973,10 @@ xml2json1_cbuf(cbuf *cb,
|
||||||
for (i=0; i<xml_child_nr(x); i++){
|
for (i=0; i<xml_child_nr(x); i++){
|
||||||
xc = xml_child_i(x, i);
|
xc = xml_child_i(x, i);
|
||||||
if (xml_type(xc) == CX_ATTR){
|
if (xml_type(xc) == CX_ATTR){
|
||||||
int ismeta = 0;
|
if (metacbp &&
|
||||||
char *namespace = NULL;
|
xml2json_encode_attr(xc, x, ys, level, pretty, modname, metacbp) < 0)
|
||||||
yang_stmt *ymod;
|
|
||||||
cbuf *metacb = NULL;
|
|
||||||
|
|
||||||
if (xml2ns(xc, xml_prefix(xc), &namespace) < 0)
|
|
||||||
goto done;
|
goto done;
|
||||||
if (namespace == NULL)
|
continue;
|
||||||
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 ((metacb = cbuf_new()) == NULL){
|
|
||||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if (json_metadata_encoding(metacb, x, level, pretty,
|
|
||||||
modname, xml_name(x),
|
|
||||||
yang_argument_get(ymod),
|
|
||||||
xml_name(xc),
|
|
||||||
xml_value(xc)) < 0)
|
|
||||||
goto done;
|
|
||||||
if (metacbp)
|
|
||||||
*metacbp = metacb;
|
|
||||||
else
|
|
||||||
cbuf_free(metacb);
|
|
||||||
continue; /* XXX Only xmlns attributes mapped */
|
|
||||||
}
|
}
|
||||||
xc_arraytype = array_eval(i?xml_child_i(x,i-1):NULL,
|
xc_arraytype = array_eval(i?xml_child_i(x,i-1):NULL,
|
||||||
xc,
|
xc,
|
||||||
|
|
@ -931,46 +985,17 @@ xml2json1_cbuf(cbuf *cb,
|
||||||
xc,
|
xc,
|
||||||
xc_arraytype,
|
xc_arraytype,
|
||||||
level+1, pretty, 0, modname0,
|
level+1, pretty, 0, modname0,
|
||||||
&metacbc) < 0)
|
metacbc) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (commas > 0) {
|
if (commas > 0) {
|
||||||
cprintf(cb, ",%s", pretty?"\n":"");
|
cprintf(cb, ",%s", pretty?"\n":"");
|
||||||
--commas;
|
--commas;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (metacbc){
|
if (cbuf_len(metacbc)){
|
||||||
cprintf(cb, "%s", cbuf_get(metacbc));
|
cprintf(cb, "%s", cbuf_get(metacbc));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0 /* 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){
|
switch (arraytype){
|
||||||
case BODY_ARRAY:
|
case BODY_ARRAY:
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -790,7 +790,6 @@ yang_metadata_init(clicon_handle h)
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
clixon_plugin_t *cp = NULL;
|
clixon_plugin_t *cp = NULL;
|
||||||
|
|
||||||
|
|
||||||
/* Create a pseudo-plugin to create extension callback to set the ietf-yang-meta
|
/* Create a pseudo-plugin to create extension callback to set the ietf-yang-meta
|
||||||
* yang-data extension for api-root top-level restconf function.
|
* yang-data extension for api-root top-level restconf function.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -557,7 +557,7 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCP
|
||||||
"HTTP/$HVER 200" \
|
"HTTP/$HVER 200" \
|
||||||
"Content-Type: application/yang-data+json" \
|
"Content-Type: application/yang-data+json" \
|
||||||
"Cache-Control: no-cache" \
|
"Cache-Control: no-cache" \
|
||||||
'{"example:interface":\[{"name":"eth1","mtu":1500,"status":"ok"}\]}'
|
'{"example:interface":\[{"name":"eth1","mtu":1500,"status":"ok","@mtu":{"ietf-netconf-with-defaults:default":true},"@status":{"ietf-netconf-with-defaults:default":true}}\]}'
|
||||||
|
|
||||||
new "rfc8040 B.3.9. RESTCONF with-defaults parameter = report-all-tagged xml"
|
new "rfc8040 B.3.9. RESTCONF with-defaults parameter = report-all-tagged xml"
|
||||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example:interfaces/interface=eth1?with-defaults=report-all-tagged)" \
|
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example:interfaces/interface=eth1?with-defaults=report-all-tagged)" \
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,7 @@ tls_server_reply_cb(int s,
|
||||||
SSL_shutdown(ssl);
|
SSL_shutdown(ssl);
|
||||||
SSL_free(ssl);
|
SSL_free(ssl);
|
||||||
clixon_event_unreg_fd(s, tls_server_reply_cb);
|
clixon_event_unreg_fd(s, tls_server_reply_cb);
|
||||||
fprintf(stdout, "Close %d remote %lu\n", seq, td.tv_sec);
|
fprintf(stdout, "Close %d remote\n", seq);
|
||||||
close(s);
|
close(s);
|
||||||
free(sd);
|
free(sd);
|
||||||
if (_connects == 0)
|
if (_connects == 0)
|
||||||
|
|
@ -365,7 +365,7 @@ tls_timeout_cb(int fd,
|
||||||
{
|
{
|
||||||
tls_accept_handle *ta = (tls_accept_handle *)arg;
|
tls_accept_handle *ta = (tls_accept_handle *)arg;
|
||||||
|
|
||||||
fprintf(stderr, "Timeout on socket:%d\n", ta->ta_ss);
|
fprintf(stderr, "Timeout(%ds) after accept on socket:%d\n", _timeout_s, ta->ta_ss);
|
||||||
exit(200);
|
exit(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue