diff --git a/CHANGELOG.md b/CHANGELOG.md index 21a8623c..06601867 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,13 @@ * RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns: * `201 Created` for created resources * `204 No Content` for replaced resources. + * identity/identityref mapped between XML and JSON + * XML uses prefixes, JSON uses module-names (previously prefixes were used in both cases) * See [RESTCONF: HTTP return codes are not according to RFC 8040](https://github.com/clicon/clixon/issues/56) * Implementation detail: due to difference between RESTCONF and NETCONF semantics, a PUT first to make en internal netconf edit-config create operation; if that fails, a replace operation is tried. * HTTP `Location:` fields added in RESTCONF POST replies * HTTP `Cache-Control: no-cache` fields added in HTTP responses (RFC Section 5.5) * Restconf monitoring capabilities (RFC Section 9.1) - * * Yang extensions support * New plugin callback: ca_extension * The main example explains how to implement a Yang extension in a backend plugin. @@ -21,6 +22,8 @@ * RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns: * `201 Created` for created resources * `204 No Content` for replaced resources. +* RESTCONF identities has been changed to use module names instead of prefixes. + * Eg, `curl -X POST -d '{"type":"ex:eth"}` --> `curl -X POST -d '{"type":"ietf-interfaces:eth"`} * JSON changes * Non-pretty-print output removed all extra spaces. * Example: `{"nacm-example:x": 42}` --> {"nacm-example:x":42}` diff --git a/apps/restconf/Makefile.in b/apps/restconf/Makefile.in index 2e9629ac..b2936775 100644 --- a/apps/restconf/Makefile.in +++ b/apps/restconf/Makefile.in @@ -78,6 +78,7 @@ APPSRC = restconf_main.c APPSRC += restconf_methods.c APPSRC += restconf_methods_post.c APPSRC += restconf_methods_get.c +APPSRC += restconf_methods_patch.c APPSRC += restconf_stream.c APPOBJ = $(APPSRC:.c=.o) diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index a1fb40a0..5a7da6b7 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -78,8 +78,9 @@ /* restconf */ #include "restconf_lib.h" #include "restconf_methods.h" -#include "restconf_methods_post.h" #include "restconf_methods_get.h" +#include "restconf_methods_post.h" +#include "restconf_methods_patch.h" #include "restconf_stream.h" /* Command line options to be passed to getopt(3) */ diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 7d84cf92..6cc87ce2 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -288,7 +288,7 @@ api_data_put(clicon_handle h, char *namespace0; char *dname; - clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", + clicon_debug(1, "%s api_path:\"%s\" data:\"%s\"", __FUNCTION__, api_path0, data); if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); @@ -336,6 +336,12 @@ api_data_put(clicon_handle h, } } else{ + /* Data here cannot cannot be Yang populated since it is loosely + * hanging without top symbols. + * And if it is not yang populated, it cant be translated properly + * from JSON to XML. + * Therefore, yang population is done later after addsub below + */ if ((ret = json_parse_str(data, yspec, &xdata0, &xerr)) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; @@ -487,6 +493,21 @@ api_data_put(clicon_handle h, xml_purge(xbot); if (xml_addsub(xparent, xdata) < 0) goto done; + /* xbot is already populated, resolve yang for added xdata too */ + if (xml_apply0(xdata, CX_ELMNT, xml_spec_populate, yspec) < 0) + goto done; + if (!parse_xml){ + /* json2xml decode could not be done above in json_parse, + need to be done here instead */ + if ((ret = json2xml_decode(xdata, &xerr)) < 0) + goto done; + if (ret == 0){ + if (api_return_err(h, r, xerr, pretty, use_xml, 0) < 0) + goto done; + goto ok; + } + } + /* If we already have that default namespace, remove it in child */ if ((xa = xml_find_type(xdata, NULL, "xmlns", CX_ATTR)) != NULL){ if (xml2ns(xparent, NULL, &namespace0) < 0) @@ -495,6 +516,7 @@ api_data_put(clicon_handle h, if (strcmp(namespace0, xml_value(xa))==0) xml_purge(xa); } + } /* Create text buffer for transfer to backend */ if ((cbx = cbuf_new()) == NULL) @@ -613,29 +635,6 @@ api_data_put(clicon_handle h, return retval; } /* api_data_put */ -/*! Generic REST PATCH method - * @param[in] h CLIXON handle - * @param[in] r Fastcgi request handle - * @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] pi Offset, where to start pcvec - * @param[in] qvec Vector of query string (QUERY_STRING) - * @param[in] data Stream input data - * Netconf: (nc:operation="merge") - * See RFC8040 Sec 4.6 - */ -int -api_data_patch(clicon_handle h, - FCGX_Request *r, - char *api_path, - cvec *pcvec, - int pi, - cvec *qvec, - char *data) -{ - notimplemented(r); - return 0; -} /*! Generic REST DELETE method translated to edit-config * @param[in] h CLIXON handle diff --git a/apps/restconf/restconf_methods.h b/apps/restconf/restconf_methods.h index 89ad6460..55aa17f0 100644 --- a/apps/restconf/restconf_methods.h +++ b/apps/restconf/restconf_methods.h @@ -46,9 +46,6 @@ int api_data_put(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi, cvec *qvec, char *data, int pretty, int use_xml, int parse_xml); -int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path, - cvec *pcvec, int pi, - cvec *qvec, char *data); int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi, int pretty, int use_xml); diff --git a/apps/restconf/restconf_methods_get.c b/apps/restconf/restconf_methods_get.c index c89b9ede..1fb8ad10 100644 --- a/apps/restconf/restconf_methods_get.c +++ b/apps/restconf/restconf_methods_get.c @@ -60,7 +60,6 @@ #include /* Need to be after clixon_xml-h due to attribute format */ #include "restconf_lib.h" -#include "restconf_methods.h" #include "restconf_methods_get.h" /*! Generic GET (both HEAD and GET) @@ -187,6 +186,17 @@ api_data_get2(clicon_handle h, goto done; } else{ +#if 0 + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xret, 0, 0) < 0) + goto done; + clicon_debug(1, "%s xret: %s", + __FUNCTION__, cbuf_get(ccc)); + cbuf_free(ccc); + } +#endif + if (xml2json_cbuf(cbx, xret, pretty) < 0) goto done; } diff --git a/apps/restconf/restconf_methods_patch.c b/apps/restconf/restconf_methods_patch.c new file mode 100644 index 00000000..39f72028 --- /dev/null +++ b/apps/restconf/restconf_methods_patch.c @@ -0,0 +1,151 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + 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 + 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, + 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 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. + + ***** END LICENSE BLOCK ***** + + */ + +/* + * See rfc8040 + + * sudo apt-get install libfcgi-dev + * gcc -o fastcgi fastcgi.c -lfcgi + + * sudo su -c "/www-data/clixon_restconf -D 1 f /usr/local/etc/example.xml " -s /bin/sh www-data + + * This is the interface: + * api/data/profile=/metric= PUT data:enable= + * api/test + +----------------------------+--------------------------------------+ + | 100 Continue | POST accepted, 201 should follow | + | 200 OK | Success with response message-body | + | 201 Created | POST to create a resource success | + | 204 No Content | Success without response message- | + | | body | + | 304 Not Modified | Conditional operation not done | + | 400 Bad Request | Invalid request message | + | 401 Unauthorized | Client cannot be authenticated | + | 403 Forbidden | Access to resource denied | + | 404 Not Found | Resource target or resource node not | + | | found | + | 405 Method Not Allowed | Method not allowed for target | + | | resource | + | 409 Conflict | Resource or lock in use | + | 412 Precondition Failed | Conditional method is false | + | 413 Request Entity Too | too-big error | + | Large | | + | 414 Request-URI Too Large | too-big error | + | 415 Unsupported Media Type | non RESTCONF media type | + | 500 Internal Server Error | operation-failed | + | 501 Not Implemented | unknown-operation | + | 503 Service Unavailable | Recoverable server error | + +----------------------------+--------------------------------------+ +Mapping netconf error-tag -> status code + +-------------------------+-------------+ + | | status code | + +-------------------------+-------------+ + | in-use | 409 | + | invalid-value | 400 | + | too-big | 413 | + | missing-attribute | 400 | + | bad-attribute | 400 | + | unknown-attribute | 400 | + | bad-element | 400 | + | unknown-element | 400 | + | unknown-namespace | 400 | + | access-denied | 403 | + | lock-denied | 409 | + | resource-denied | 409 | + | rollback-failed | 500 | + | data-exists | 409 | + | data-missing | 409 | + | operation-not-supported | 501 | + | operation-failed | 500 | + | partial-operation | 500 | + | malformed-message | 400 | + +-------------------------+-------------+ + + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include + +#include /* Need to be after clixon_xml-h due to attribute format */ + +#include "restconf_lib.h" +#include "restconf_methods_patch.h" + + +/*! Generic REST PATCH method + * @param[in] h CLIXON handle + * @param[in] r Fastcgi request handle + * @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] pi Offset, where to start pcvec + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] data Stream input data + * Netconf: (nc:operation="merge") + * See RFC8040 Sec 4.6 + */ +int +api_data_patch(clicon_handle h, + FCGX_Request *r, + char *api_path, + cvec *pcvec, + int pi, + cvec *qvec, + char *data) +{ + notimplemented(r); + return 0; +} + diff --git a/apps/restconf/restconf_methods_patch.h b/apps/restconf/restconf_methods_patch.h new file mode 100644 index 00000000..7149b07e --- /dev/null +++ b/apps/restconf/restconf_methods_patch.h @@ -0,0 +1,48 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + 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 + 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, + 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 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. + + ***** END LICENSE BLOCK ***** + + * Restconf method implementation + */ + + +#ifndef _RESTCONF_METHODS_PATCH_H_ +#define _RESTCONF_METHODS_PATCH_H_ + +/* + * Prototypes + */ +int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path, + cvec *pcvec, int pi, + cvec *qvec, char *data); + +#endif /* _RESTCONF_METHODS_PATCH_H_ */ diff --git a/apps/restconf/restconf_methods_post.c b/apps/restconf/restconf_methods_post.c index 104b37ce..d88bbc75 100644 --- a/apps/restconf/restconf_methods_post.c +++ b/apps/restconf/restconf_methods_post.c @@ -62,7 +62,6 @@ #include /* Need to be after clixon_xml.h due to attribute format */ #include "restconf_lib.h" -#include "restconf_methods.h" #include "restconf_methods_post.h" /*! Generic REST POST method @@ -129,9 +128,10 @@ api_data_post(clicon_handle h, cxobj *xerr = NULL; /* malloced must be freed */ cxobj *xe; /* dont free */ char *username; + int nullspec = 0; int ret; - clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", + clicon_debug(1, "%s api_path:\"%s\" data:\"%s\"", __FUNCTION__, api_path, data); if ((yspec = clicon_dbspec_yang(h)) == NULL){ @@ -178,6 +178,15 @@ api_data_post(clicon_handle h, } } else { + /* Data here cannot cannot (always) be Yang populated since it is + * loosely hanging without top symbols. + * And if it is not yang populated, it cant be translated properly + * from JSON to XML. + * Therefore, yang population is done later after addsub below + * Further complication is that if data is root resource, then it will + * work, so I need to check below that it didnt. + * THIS could be simplified. + */ if ((ret = json_parse_str(data, yspec, &xdata0, &xerr)) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; @@ -245,9 +254,24 @@ api_data_post(clicon_handle h, /* Replace xbot with x, ie bottom of api-path with data */ if (xml_addsub(xbot, xdata) < 0) goto done; - /* xbot is already populated, resolve yang for added xdata too */ - if (xml_spec_populate(xdata, yspec) < 0) + /* xbot is already populated, resolve yang for added xdata too + */ + nullspec = (xml_spec(xdata) == NULL); + if (xml_apply0(xdata, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; + if (!parse_xml && nullspec){ + /* json2xml decode may not have been done above in json_parse, + need to be done here instead + UNLESS it is a root resource, then json-parse does right + */ + if ((ret = json2xml_decode(xdata, &xerr)) < 0) + goto done; + if (ret == 0){ + if (api_return_err(h, r, xerr, pretty, use_xml, 0) < 0) + goto done; + goto ok; + } + } /* Create text buffer for transfer to backend */ if ((cbx = cbuf_new()) == NULL){ diff --git a/include/clixon_custom.h b/include/clixon_custom.h index 424790fb..c60b8101 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -57,4 +57,4 @@ * The easy way to do this is to always generate all prefix/namespace bindings * on the top-level for the modules involved in the netconf operation. */ -#undef IDENTITYREF_KLUDGE +#define IDENTITYREF_KLUDGE diff --git a/lib/clixon/clixon_json.h b/lib/clixon/clixon_json.h index 30488d0b..f218e6b2 100644 --- a/lib/clixon/clixon_json.h +++ b/lib/clixon/clixon_json.h @@ -41,6 +41,7 @@ /* * Prototypes */ +int json2xml_decode(cxobj *x, cxobj **xerr); int xml2json_cbuf(cbuf *cb, cxobj *x, int pretty); int xml2json_cbuf_vec(cbuf *cb, cxobj **vec, size_t veclen, int pretty); int xml2json(FILE *f, cxobj *x, int pretty); diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 4070eac2..a0bd8498 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -170,6 +170,7 @@ yang_stmt *ys_module(yang_stmt *ys); yang_stmt *ys_real_module(yang_stmt *ys); yang_stmt *ys_spec(yang_stmt *ys); yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix); +yang_stmt *yang_find_module_by_prefix_yspec(yang_stmt *yspec, char *prefix); yang_stmt *yang_find_module_by_namespace(yang_stmt *yspec, char *namespace); yang_stmt *yang_find_module_by_name(yang_stmt *yspec, char *name); yang_stmt *yang_find(yang_stmt *yn, int keyword, const char *argument); @@ -178,6 +179,7 @@ yang_stmt *yang_find_datanode(yang_stmt *yn, char *argument); yang_stmt *yang_find_schemanode(yang_stmt *yn, char *argument); char *yang_find_myprefix(yang_stmt *ys); char *yang_find_mynamespace(yang_stmt *ys); +int yang_find_prefix_by_namespace(yang_stmt *ys, char *namespace, char **prefix); yang_stmt *yang_choice(yang_stmt *y); int yang_order(yang_stmt *y); int yang_print(FILE *f, yang_stmt *yn); diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index 609e7e07..23c9389b 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -36,6 +36,10 @@ * http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf */ +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + #include #include #include @@ -64,6 +68,7 @@ #include "clixon_xml.h" #include "clixon_xml_sort.h" #include "clixon_xml_map.h" +#include "clixon_xml_nsctx.h" /* namespace context */ #include "clixon_netconf_lib.h" #include "clixon_json.h" #include "clixon_json_parse.h" @@ -224,7 +229,8 @@ array_eval(cxobj *xprev, } /*! Escape a json string as well as decode xml cdata - * And a + * @param[out] cb cbuf (encoded) + * @param[in] str string (unencoded) */ static int json_str_escape_cdata(cbuf *cb, @@ -272,25 +278,287 @@ json_str_escape_cdata(cbuf *cb, return retval; } -/*! If set, quoute the json value with double quotes - * @þaram[in] xb XML body object - @ @retval 0 Value should not be quouted, XML value is int, boolean,.. - @ @retval 1 Value should be quouted, XML value is string,.. +/*! Decode types from JSON to XML identityrefs + * 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. + * @param[in] yspec Yang spec + * @param[out] xerr Reason for invalid tree returned as netconf err msg or NULL + * @retval 1 OK + * @retval 0 Invalid, wrt namespace. xerr set + * @retval -1 Error + * @see RFC7951 Sec 4 and 6.8 */ static int -jsonvaluestr(cxobj *xb) +json2xml_decode_identityref(cxobj *x, + yang_stmt *y, + cxobj **xerr) { - int retval = 1; - cxobj *xp; - yang_stmt *yp; - enum rfc_6020 keyword; + int retval = -1; + char *namespace; + char *body; + cxobj *xb; + cxobj *xa; + char *prefix = NULL; + char *id = NULL; + yang_stmt *ymod; + yang_stmt *yspec; + cvec *nsc = NULL; + char *prefix2 = NULL; + cbuf *cbv = NULL; + + clicon_debug(1, "%s", __FUNCTION__); + yspec = ys_spec(y); + if ((xb = xml_body_get(x)) == NULL) + goto ok; + body = xml_value(xb); + if (nodeid_split(body, &prefix, &id) < 0) + goto done; + /* prefix is a module name -> find module */ + if (prefix){ + if ((ymod = yang_find_module_by_name(yspec, prefix)) != NULL){ + namespace = yang_find_mynamespace(ymod); + /* Is this namespace in the xml context? + * (yes) use its prefix (unless it is NULL) + * (no) insert a xmlns: statement + * Get the whole namespace context from x + */ + if (xml_nsctx_node(x, &nsc) < 0) + goto done; + clicon_debug(1, "%s prefix:%s body:%s namespace:%s", + __FUNCTION__, prefix, body, namespace); + if (!xml_nsctx_get_prefix(nsc, namespace, &prefix2)){ + /* (no) insert a xmlns: statement + * Get yang prefix from import statement of my mod */ + if (yang_find_prefix_by_namespace(y, namespace, &prefix2) == 0){ +#ifndef IDENTITYREF_KLUDGE + /* Just get the prefix from the module's own namespace */ + if (netconf_unknown_namespace_xml(xerr, "application", + namespace, + "No local prefix corresponding to namespace") < 0) + goto done; + goto fail; +#endif + } + /* if prefix2 is NULL here, we get the canonical prefix */ + if (prefix2 == NULL) + prefix2 = yang_find_myprefix(ymod); + /* Add "xmlns:prefix2=namespace" */ + if ((xa = xml_new(prefix2, x, NULL)) == NULL) + goto done; + xml_type_set(xa, CX_ATTR); + if (xml_prefix_set(xa, "xmlns") < 0) + goto done; + if (xml_value_set(xa, namespace) < 0) + goto done; + } + /* Here prefix2 is valid and can be NULL + Change body prefix to prefix2:id */ + if ((cbv = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + if (prefix2) + cprintf(cbv, "%s:%s", prefix2, id); + else + cprintf(cbv, "%s", id); + if (xml_value_set(xb, cbuf_get(cbv)) < 0) + goto done; + } + else{ + if (netconf_unknown_namespace_xml(xerr, "application", + prefix, + "No module corresponding to prefix") < 0) + goto done; + goto fail; + } + } /* prefix */ + ok: + retval = 1; + done: + if (prefix) + free(prefix); + if (id) + free(id); + if (nsc) + xml_nsctx_free(nsc); + if (cbv) + cbuf_free(cbv); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Decode leaf/leaf_list types from JSON to XML after parsing and yang + * + * 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 0 Invalid, wrt namespace. xerr set + * @retval -1 Error + * @see RFC7951 Sec 4 and 6.8 + */ +int +json2xml_decode(cxobj *x, + cxobj **xerr) +{ + int retval = -1; + yang_stmt *y; + enum rfc_6020 keyword; + cxobj *xc; + int ret; + yang_stmt *ytype; + + if ((y = xml_spec(x)) != NULL){ + keyword = yang_keyword_get(y); + if (keyword == Y_LEAF || keyword == Y_LEAF_LIST){ + if (yang_type_get(y, NULL, &ytype, NULL, NULL, NULL, NULL, NULL) < 0) + goto done; + if (ytype) + if (strcmp(yang_argument_get(ytype),"identityref")==0){ + if ((ret = json2xml_decode_identityref(x, y, xerr)) < 0) + goto done; + if (ret == 0) + goto fail; + } + } + } + xc = NULL; + while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL){ + if ((ret = json2xml_decode(xc, xerr)) < 0) + goto done; + if (ret == 0) + goto fail; + } + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + +/*! Encode leaf/leaf_list identityref type from XML to JSON + * @param[in] x XML body node + * @param[in] body body string + * @param[in] ys Yang spec of parent + * @param[out] cb Encoded string + */ +static int +xml2json_encode_identityref(cxobj *xb, + char *body, + yang_stmt *yp, + cbuf *cb) +{ + int retval = -1; + char *prefix = NULL; + char *id = NULL; + char *namespace = NULL; + yang_stmt *ymod; + yang_stmt *yspec; + yang_stmt *my_ymod; + + my_ymod = ys_module(yp); + yspec = ys_spec(yp); + if (nodeid_split(body, &prefix, &id) < 0) + goto done; + /* prefix is xml local -> get namespace */ + if (xml2ns(xb, prefix, &namespace) < 0) + goto done; + /* We got the namespace, now get the module */ + // clicon_debug(1, "%s body:%s prefix:%s namespace:%s", __FUNCTION__, body, prefix, namespace); +#ifdef IDENTITYREF_KLUDGE + if (namespace == NULL){ + /* If we dont find namespace here, we assume it is because of a missing + * xmlns that should be there, as a kludge we search for its (own) + * prefix in mymodule. + */ + if ((ymod = yang_find_module_by_prefix_yspec(yspec, prefix)) != NULL) + cprintf(cb, "%s:%s", yang_argument_get(ymod), id); + else + cprintf(cb, "%s", id); + } + else +#endif + { + if ((ymod = yang_find_module_by_namespace(yspec, namespace)) != NULL){ + + if (ymod == my_ymod) + cprintf(cb, "%s", id); + else{ + cprintf(cb, "%s:%s", yang_argument_get(ymod), id); + } + } + else + cprintf(cb, "%s", id); + } + retval = 0; + done: + if (prefix) + free(prefix); + if (id) + free(id); + return retval; +} + +/*! Encode leaf/leaf_list types from XML to JSON + * @param[in] x XML body + * @param[in] ys Yang spec of parent + * @param[out] cb0 Encoded string + */ +static int +xml2json_encode(cxobj *xb, + cbuf *cb0) +{ + int retval = -1; + cxobj *xp; + yang_stmt *yp; + enum rfc_6020 keyword; + yang_stmt *ytype; + char *restype; /* resolved type */ + char *origtype=NULL; /* original type */ + char *body; + enum cv_type cvtype; + int quote = 1; /* Quote value w string: "val" */ + cbuf *cb = NULL; /* the variable itself */ + + if ((cb = cbuf_new()) ==NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + body = xml_value(xb); if ((xp = xml_parent(xb)) == NULL || - (yp = xml_spec(xp)) == NULL) - goto done; /* unknown */ + (yp = xml_spec(xp)) == NULL){ + cprintf(cb, "%s", body); + goto ok; /* unknown */ + } keyword = yang_keyword_get(yp); - if ((keyword == Y_LEAF || keyword == Y_LEAF_LIST)) - switch (yang_type2cv(yp)){ + switch (keyword){ + case Y_LEAF: + case Y_LEAF_LIST: + if (yang_type_get(yp, &origtype, &ytype, NULL, NULL, NULL, NULL, NULL) < 0) + goto done; + restype = ytype?yang_argument_get(ytype):NULL; + cvtype = yang_type2cv(yp); + switch (cvtype){ + case CGV_STRING: + if (ytype){ + if (strcmp(restype, "identityref")==0){ + if (xml2json_encode_identityref(xb, body, yp, cb) < 0) + goto done; + } + else + cprintf(cb, "%s", body); + } + else + cprintf(cb, "%s", body); + break; case CGV_INT8: case CGV_INT16: case CGV_INT32: @@ -301,18 +569,42 @@ jsonvaluestr(cxobj *xb) case CGV_UINT64: case CGV_DEC64: case CGV_BOOL: - retval = 0; + cprintf(cb, "%s", body); + quote = 0; break; default: - break; + cprintf(cb, "%s", body); } + break; + default: + cprintf(cb, "%s", body); + break; + } + ok: + /* write into original cb0 + * includign quoting and encoding + */ + if (quote){ + cprintf(cb0, "\""); + json_str_escape_cdata(cb0, cbuf_get(cb)); + } + else + cprintf(cb0, "%s", cbuf_get(cb)); + if (quote) + cprintf(cb0, "\""); + retval = 0; done: + if (cb) + cbuf_free(cb); + if (origtype) + free(origtype); 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 + * @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] level Indentation level * @param[in] pretty Pretty-print output (2 means debug) @@ -381,17 +673,10 @@ xml2json1_cbuf(cbuf *cb, arraytype2str(arraytype), childtype2str(childt)); switch(arraytype){ - case BODY_ARRAY:{ - if (jsonvaluestr(x)) { /* Only print quotation if string-type */ - cprintf(cb, "\""); - if (json_str_escape_cdata(cb, xml_value(x)) < 0) - goto done; - cprintf(cb, "\""); - } - else /* No quotation marks */ - cprintf(cb, "%s", xml_value(x)); + case BODY_ARRAY: /* Only place in fn where body is printed */ + if (xml2json_encode(x, cb) < 0) + goto done; break; - } case NO_ARRAY: if (!flat){ cprintf(cb, "%*s\"", pretty?(level*JSON_INDENT):0, ""); @@ -739,7 +1024,7 @@ xml2json_vec(FILE *f, /*! Translate from JSON module:name to XML default ns: xmlns="uri" recursively * 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 to XML namespace trees * * @param[in] yspec Yang spec * @param[in,out] x XML tree. Translate it in-line @@ -749,8 +1034,9 @@ xml2json_vec(FILE *f, * @retval -1 Error * @note the opposite - xml2ns is made inline in xml2json1_cbuf * Example: --> + * @see RFC7951 Sec 4 */ -int +static int json_xmlns_translate(yang_stmt *yspec, cxobj *x, cxobj **xerr) @@ -799,7 +1085,7 @@ json_xmlns_translate(yang_stmt *yspec, retval = 0; goto done; } - + /*! Parse a string containing JSON and return an XML tree * * Parsing using yacc according to JSON syntax. Names with : @@ -809,17 +1095,17 @@ json_xmlns_translate(yang_stmt *yspec, * @param[in] yspec If set, also do yang validation * @param[in] name Log string, typically filename * @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 * * @see _xml_parse for XML variant - * @retval 1 OK and valid - * @retval 0 Invalid (only if yang spec) - * @retval -1 Error with clicon_err called + * @retval 1 OK and valid + * @retval 0 Invalid (only if yang spec) + * @retval -1 Error with clicon_err called * @see http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf * @see RFC 7951 */ static int -json_parse(char *str, +json_parse(char *str, yang_stmt *yspec, const char *name, cxobj *xt, @@ -855,6 +1141,12 @@ json_parse(char *str, goto done; if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0) goto done; + /* Now find leafs with identityrefs (+transitive) and translate + * prefixes in values to XML namespaces */ + if ((ret = json2xml_decode(xt, xerr)) < 0) + goto done; + if (ret == 0) /* XXX necessary? */ + goto fail; } retval = 1; done: @@ -871,7 +1163,7 @@ json_parse(char *str, * * @param[in] str String containing JSON * @param[in] yspec Yang specification, or NULL - * @param[in,out] xt On success a top of XML parse tree 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 * * @code diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 992a960e..e7d7e371 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -279,7 +279,7 @@ nscache_set(cxobj *x, * @param[out] namespace URI namespace (or NULL). Note pointer into xml tree * @retval 0 OK * @retval -1 Error - * @see xmlns_check XXX can these be merged? + * @see xmlns_check * @see xmlns_set cache is set * @note, this function uses a cache. */ @@ -307,8 +307,12 @@ xml2ns(cxobj *x, } /* If no parent, return default namespace if defined */ #ifdef USE_NETCONF_NS_AS_DEFAULT - else - ns = NETCONF_BASE_NAMESPACE; + else{ + if (prefix == NULL) + ns = NETCONF_BASE_NAMESPACE; + else + ns = NULL; + } #endif } /* Set default namespace cache (since code is at this point, diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index df56244f..565f1943 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -332,24 +332,6 @@ validate_leafref(cxobj *xt, goto done; } -/* Get module from its own prefix - * This is really not a valid usecase, a kludge for the identityref derived - * list workaround (IDENTITYREF_KLUDGE) - */ -static yang_stmt * -yang_find_module_by_prefix_yspec(yang_stmt *yspec, - char *prefix) -{ - yang_stmt *ymod = NULL; - yang_stmt *yprefix; - - while ((ymod = yn_each(yspec, ymod)) != NULL) - if (ymod->ys_keyword == Y_MODULE && - (yprefix = yang_find(ymod, Y_PREFIX, NULL)) != NULL && - strcmp(yang_argument_get(yprefix), prefix) == 0) - return ymod; - return NULL; -} /*! Validate xml node of type identityref, ensure value is a defined identity * Check if a given node has value derived from base identity. This is @@ -2340,6 +2322,7 @@ xml_spec_populate_rpc(clicon_handle h, * xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) * @endcode */ +#undef DEBUG int xml_spec_populate(cxobj *x, void *arg) @@ -2355,17 +2338,36 @@ xml_spec_populate(cxobj *x, yspec = (yang_stmt*)arg; xp = xml_parent(x); name = xml_name(x); +#ifdef DEBUG + clicon_debug(1, "%s name:%s", __FUNCTION__, name); +#endif if (xp && (yparent = xml_spec(xp)) != NULL) y = yang_find_datanode(yparent, name); else if (yspec){ if (ys_module_by_xml(yspec, x, &ymod) < 0) goto done; /* ymod is "real" module, name may belong to included submodule */ - if (ymod != NULL) + if (ymod != NULL){ +#ifdef DEBUG + clicon_debug(1, "%s %s mod:%s", __FUNCTION__, name, yang_argument_get(ymod)); +#endif y = yang_find_schemanode(ymod, name); + } +#ifdef DEBUG + else + clicon_debug(1, "%s %s mod:NULL", __FUNCTION__, name); +#endif } - if (y) + if (y) { +#ifdef DEBUG + clicon_debug(1, "%s y:%s", __FUNCTION__, yang_argument_get(y)); +#endif xml_spec_set(x, y); + } +#ifdef DEBUG + else + clicon_debug(1, "%s y:NULL", __FUNCTION__); +#endif retval = 0; done: return retval; diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 7496f465..30c11d57 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -795,6 +795,64 @@ yang_find_mynamespace(yang_stmt *ys) return namespace; } +/*! Given a yang statement and namespace, find local prefix valid in module + * This is useful if you want to make a "reverse" lookup, you know the + * (global) namespace of a module, but you do not know the local prefix + * used to access it in XML. + * @param[in] ys Yang statement in module tree (or module itself) + * @param[in] namespace Namspace URI as char* pointer into yang tree + * @param[out] prefix Local prefix to access module with (direct pointer) + * @retval 0 not found + * @retval -1 found + * @code + * @note prefix NULL is not returned, if own module, then return its prefix + * char *pfx = yang_find_prefix_by_namespace(ys, "urn:example:clixon", &prefix); + * @endcode + */ +int +yang_find_prefix_by_namespace(yang_stmt *ys, + char *namespace, + char **prefix) +{ + int retval = 0; /* not found */ + yang_stmt *my_ymod; /* My module */ + char *myns; /* My ns */ + yang_stmt *yspec; + yang_stmt *ymod; + char *modname = NULL; + yang_stmt *yimport; + yang_stmt *yprefix; + + clicon_debug(1, "%s", __FUNCTION__); + /* First check if namespace is my own module */ + myns = yang_find_mynamespace(ys); + if (strcmp(myns, namespace) == 0){ + *prefix = yang_find_myprefix(ys); /* or NULL? */ + goto found; + } + /* Next, find namespaces in imported modules */ + yspec = ys_spec(ys); + if ((ymod = yang_find_module_by_namespace(yspec, namespace)) == NULL) + goto notfound; + modname = yang_argument_get(ymod); + my_ymod = ys_module(ys); + /* Loop through import statements to find a match with ymod */ + yimport = NULL; + while ((yimport = yn_each(my_ymod, yimport)) != NULL) { + if (yang_keyword_get(yimport) == Y_IMPORT && + strcmp(modname, yang_argument_get(yimport)) == 0){ /* match */ + yprefix = yang_find(yimport, Y_PREFIX, NULL); + *prefix = yang_argument_get(yprefix); + goto found; + } + } + notfound: + return retval; + found: + assert(*prefix); + return 1; +} + /*! If a given yang stmt has a choice/case as parent, return the choice statement */ yang_stmt * @@ -985,16 +1043,8 @@ ys_module_by_xml(yang_stmt *ysp, if (ymodp) *ymodp = NULL; prefix = xml_prefix(xt); - if (prefix){ - /* Get namespace for prefix */ - if (xml2ns(xt, prefix, &namespace) < 0) - goto done; - } - else{ - /* Get default namespace */ - if (xml2ns(xt, NULL, &namespace) < 0) - goto done; - } + if (xml2ns(xt, prefix, &namespace) < 0) /* prefix may be NULL */ + goto done; /* No namespace found, give up */ if (namespace == NULL) goto ok; @@ -1134,8 +1184,7 @@ yang_find_module_by_prefix(yang_stmt *ys, } yimport = NULL; while ((yimport = yn_each(my_ymod, yimport)) != NULL) { - if (yang_keyword_get(yimport) != Y_IMPORT && - yang_keyword_get(yimport) != Y_INCLUDE) + if (yang_keyword_get(yimport) != Y_IMPORT) continue; if ((yprefix = yang_find(yimport, Y_PREFIX, NULL)) != NULL && strcmp(yang_argument_get(yprefix), prefix) == 0){ @@ -1143,8 +1192,7 @@ yang_find_module_by_prefix(yang_stmt *ys, } } if (yimport){ - if ((ymod = yang_find(yspec, Y_MODULE, yang_argument_get(yimport))) == NULL && - (ymod = yang_find(yspec, Y_SUBMODULE, yang_argument_get(yimport))) == NULL){ + if ((ymod = yang_find(yspec, Y_MODULE, yang_argument_get(yimport))) == NULL){ clicon_err(OE_YANG, 0, "No module or sub-module found with prefix %s", prefix); yimport = NULL; @@ -1155,6 +1203,25 @@ yang_find_module_by_prefix(yang_stmt *ys, return ymod; } +/* Get module from its own prefix + * This is really not a valid usecase, a kludge for the identityref derived + * list workaround (IDENTITYREF_KLUDGE) + */ +yang_stmt * +yang_find_module_by_prefix_yspec(yang_stmt *yspec, + char *prefix) +{ + yang_stmt *ymod = NULL; + yang_stmt *yprefix; + + while ((ymod = yn_each(yspec, ymod)) != NULL) + if (ymod->ys_keyword == Y_MODULE && + (yprefix = yang_find(ymod, Y_PREFIX, NULL)) != NULL && + strcmp(yang_argument_get(yprefix), prefix) == 0) + return ymod; + return NULL; +} + /*! Given a yang spec and a namespace, return yang module * * @param[in] yspec A yang specification diff --git a/test/test_json.sh b/test/test_json.sh index 9c974f67..e3994483 100755 --- a/test/test_json.sh +++ b/test/test_json.sh @@ -8,12 +8,26 @@ s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi : ${clixon_util_json:=clixon_util_json} +: ${clixon_util_xml:=clixon_util_xml} fyang=$dir/json.yang cat < $fyang module json{ prefix ex; namespace "urn:example:clixon"; + identity genre { + description + "From RFC8040 jukebox example. + Identity prefixes are translated from module-name to xml prefix"; + } + identity blues { + base genre; + } + typedef gtype{ + type identityref{ + base genre; + } + } leaf a{ type int32; } @@ -25,6 +39,14 @@ module json{ type string; } } + leaf g1 { + description "direct type"; + type identityref { base genre; } + } + leaf g2 { + description "indirect type"; + type gtype; + } } EOF @@ -68,18 +90,42 @@ expecteofeq "$clixon_util_json -jpy $fyang" 0 "$JSON" "$JSONP" JSON='{"json:a":-23}' new "json leaf back to json" -expecteofx "$clixon_util_json -j -y $fyang" 0 "$JSON" "$JSON" +expecteofx "$clixon_util_json -jy $fyang" 0 "$JSON" "$JSON" JSON='{"json:c":{"a":937}}' new "json parse container back to json" -expecteofx "$clixon_util_json -j -y $fyang" 0 "$JSON" "$JSON" +expecteofx "$clixon_util_json -jy $fyang" 0 "$JSON" "$JSON" -# This should work +# identities translation json -> xml is tricky wrt prefixes, json uses module +# name, xml uses xml namespace prefixes (or default) +JSON='{"json:g1":"json:blues"}' + +new "json identity to xml" +expecteofx "$clixon_util_json -y $fyang" 0 "$JSON" 'blues' + +new "json identity back to json" +expecteofx "$clixon_util_json -jy $fyang" 0 "$JSON" '{"json:g1":"blues"}' + +new "xml identity with explicit ns to json" +expecteofx "$clixon_util_xml -ovjy $fyang" 0 'ex:blues' '{"json:g1":"blues"}' + +# Same with indirect type +JSON='{"json:g2":"json:blues"}' + +new "json indirect identity to xml" +expecteofx "$clixon_util_json -y $fyang" 0 "$JSON" 'blues' + +new "json indirect identity back to json" +expecteofx "$clixon_util_json -jy $fyang" 0 "$JSON" '{"json:g2":"blues"}' + +new "xml indirect identity with explicit ns to json" +expecteofx "$clixon_util_xml -ojvy $fyang" 0 'ex:blues' '{"json:g2":"blues"}' + +# XXX CDATA translation, should work bit does not if false; then JSON='{"json:c": {"s": " x & x < y ]]>"}}' new "json parse cdata xml" expecteofx "$clixon_util_json -j -y $fyang" 0 "$JSON" "$JSON" fi - rm -rf $dir diff --git a/test/test_perf_state.sh b/test/test_perf_state.sh index 17d3ef1b..a79bfffc 100755 --- a/test/test_perf_state.sh +++ b/test/test_perf_state.sh @@ -98,8 +98,8 @@ new "netconf get $perfreq single reqs" done | $clixon_netconf -qf $cfg > /dev/null; } 2>&1 | awk '/real/ {print $2}' # RESTCONF get -new "restconf get test single req XXX" -expecteq "$(curl -s -X GET http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1)" 0 '{"ietf-interfaces:interface":[{"name":"e1","type":"ex:eth","enabled":true,"oper-status":"up"}]} +new "restconf get test single req" +expecteq "$(curl -s -X GET http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1)" 0 '{"ietf-interfaces:interface":[{"name":"e1","type":"clixon-example:eth","enabled":true,"oper-status":"up"}]} ' new "restconf get $perfreq single reqs" diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 4f0c6b36..aa579de2 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -160,16 +160,16 @@ expecteq "$(curl -s -X GET http://localhost/restconf/data/clixon-example:state)" # Exact match new "restconf Add subtree eth/0/0 to datastore using POST" -expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 'HTTP/1.1 201 Created' +expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}} http://localhost/restconf/data' 0 'HTTP/1.1 201 Created' new "restconf Re-add subtree eth/0/0 which should give error" -expectfn 'curl -s -X POST -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}' +expectfn 'curl -s -X POST -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}' # XXX Cant get this to work -#expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"ex:eth\",\"enabled\":true}}} http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}' +#expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"clixon-example:eth\",\"enabled\":true}}} http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}' new "restconf Check interfaces eth/0/0 added" -expectfn "curl -s -X GET http://localhost/restconf/data/ietf-interfaces:interfaces" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"ex:eth","enabled":true,"oper-status":"up"}]}}} +expectfn "curl -s -X GET http://localhost/restconf/data/ietf-interfaces:interfaces" 0 '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up"}\]}}} ' new "restconf delete interfaces" @@ -179,15 +179,12 @@ new "restconf Check empty config" expectfn "curl -sG http://localhost/restconf/data/clixon-example:state" 0 "$state " -# XXX: gives -# new "restconf Add interfaces subtree eth/0/0 using POST" -expectfn 'curl -s -X POST -d {"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}} http://localhost/restconf/data/ietf-interfaces:interfaces' 0 "" -# XXX cant get this to work -#expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type\":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces)" 0 "" +expectpart "$(curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}')" 0 "" +#expectfn 'curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces -d {"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' 0 "" new "restconf Check eth/0/0 added config" -expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"ex:eth","enabled":true,"oper-status":"up"}]}} +expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up"}]}} ' new "restconf Check eth/0/0 added state" @@ -195,7 +192,7 @@ expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state)" 0 ' ' new "restconf Re-post eth/0/0 which should generate error" -expecteq "$(curl -s -X POST -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}} ' +expecteq "$(curl -s -X POST -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}} ' new "Add leaf description using POST" expecteq "$(curl -s -X POST -d '{"ietf-interfaces:description":"The-first-interface"}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "" @@ -204,7 +201,7 @@ new "Add nothing using POST" expectfn 'curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":" on line 1: syntax error at or before:' new "restconf Check description added" -expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","description":"The-first-interface","type":"ex:eth","enabled":true,"oper-status":"up"}]}} +expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","description":"The-first-interface","type":"clixon-example:eth","enabled":true,"oper-status":"up"}]}} ' new "restconf delete eth/0/0" @@ -217,10 +214,10 @@ new "restconf Re-Delete eth/0/0 using none should generate error" expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-missing","error-severity":"error","error-message":"Data does not exist; cannot delete resource"}}} ' new "restconf Add subtree eth/0/0 using PUT" -expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "" +expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "" new "restconf get subtree" -expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"ex:eth","enabled":true,"oper-status":"up"}]}} +expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up"}]}} ' new "restconf rpc using POST json" @@ -262,10 +259,10 @@ if [ -z "$match" ]; then fi new "restconf Add subtree without key (expected error)" -expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key, expected '"'"'=restval'"'"'"}}} ' +expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key, expected '"'"'=restval'"'"'"}}} ' new "restconf Add subtree with too many keys (expected error)" -expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}} ' +expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}} ' new "Kill restconf daemon" stop_restconf diff --git a/test/test_restconf_jukebox.sh b/test/test_restconf_jukebox.sh index 18703884..2429b6e3 100755 --- a/test/test_restconf_jukebox.sh +++ b/test/test_restconf_jukebox.sh @@ -337,13 +337,12 @@ expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+xml' ht new "4.5. PUT replace content" # XXX should be: jbox:alternative --> example-jukebox:alternative -expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '{"example-jukebox:album":[{"name":"Wasting Light","genre":"jbox:alternative","year":2011}]}')" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '{"example-jukebox:album":[{"name":"Wasting Light","genre":"example-jukebox:alternative","year":2011}]}')" 0 "HTTP/1.1 204 No Content" new "4.5. PUT replace content (xml encoding)" expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d 'Wasting Lightjbox:alternative2011')" 0 "HTTP/1.1 204 No Content" -new "4.5. PUT create new" -# XXX should be: jbox:alternative --> example-jukebox:alternative +new "4.5. PUT create new identity" expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":[{"name":"London Calling","year":1979}]}')" 0 "HTTP/1.1 201 Created" diff --git a/test/test_xml.sh b/test/test_xml.sh index c3943ea5..8b51ccb1 100755 --- a/test/test_xml.sh +++ b/test/test_xml.sh @@ -8,6 +8,8 @@ s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi : ${clixon_util_xml:=clixon_util_xml -o} # -o is output +sleep 1 # mysterious fail, maybe this helps? + new "xml parse" expecteof "$clixon_util_xml" 0 "" "^$" diff --git a/util/clixon_util_json.c b/util/clixon_util_json.c index 9b8e338b..5ad62f2a 100644 --- a/util/clixon_util_json.c +++ b/util/clixon_util_json.c @@ -74,7 +74,7 @@ usage(char *argv0) "where options are\n" "\t-h \t\tHelp\n" "\t-D \tDebug\n" - "\t-j \t\tOutput as JSON\n" + "\t-j \t\tOutput as JSON (default is as XML)\n" "\t-l \tLog on (s)yslog, std(e)rr, std(o)ut (stderr is default)\n" "\t-p \t\tPretty-print output\n" "\t-y \tyang filename to parse (must be stand-alone)\n" ,