From fd91bb2933a4ed0f6be5295a830800c7f7f1df84 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 30 Jul 2017 16:16:10 +0200 Subject: [PATCH] restconf RPC --- CHANGELOG.md | 13 +-- apps/cli/cli_common.c | 2 +- apps/cli/cli_show.c | 2 +- apps/netconf/netconf_plugin.c | 38 +++----- apps/restconf/restconf_main.c | 37 +++++++- apps/restconf/restconf_methods.c | 121 ++++++++++++++++++++++++- apps/restconf/restconf_methods.h | 4 + datastore/keyvalue/clixon_keyvalue.c | 4 +- datastore/text/clixon_xmldb_text.c | 2 +- lib/clixon/clixon_xml_map.h | 13 +-- lib/clixon/clixon_yang.h | 6 +- lib/src/clixon_xml.c | 5 +- lib/src/clixon_xml_map.c | 53 +++++++---- lib/src/clixon_yang.c | 128 +++++++++++++++++++++++---- lib/src/clixon_yang_type.c | 17 ++-- test/test_netconf.sh | 3 + test/test_restconf.sh | 32 +++---- 17 files changed, 371 insertions(+), 109 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26b9d196..6f20b97b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Upcoming 3.3.2 ### Known issues +* Please use text datastore, key-value datastore no up-to-date +* Restconf RPC does not encode output correct ### Major changes: * Added support for YANG anyxml. @@ -50,13 +52,12 @@ If you submit "nopresence" without a leaf, it will automatically be removed: ``` -* Added YANG RPC support for netconf and CLI. With example rpc documentation and testcase. This replaces the previous "downcall" mechanism. - * This means you can make netconf rpc calls as defined by YANG. +* Added YANG RPC support for netconf, restconf and CLI. With example rpc documentation and testcase. This replaces the previous "downcall" mechanism. + * This means you can make netconf/restconf rpc calls * However you need to register an RPC backend callback using the backend_rpc_cb_register() function. See documentation and example for more details. - * Note that RESTCONF PUT for RCP calls is not yet supported. * Example, the following YANG RPC definition enables you to run a netconf rpc. ``` - YANG: + YANG: rpc myrpc { input { leaf name { @@ -64,8 +65,10 @@ If you submit "nopresence" without a leaf, it will automatically be removed: } } } - NETCONF: + NETCONF: hello + RESTCONF: + curl -sS -X POST -d {"input":{"name":"hello"}} http://localhost/restconf/operations/myroute' ``` * Enhanced leafref functionality: diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index a507bd9c..c3a4314b 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -236,7 +236,7 @@ cli_dbxml(clicon_handle h, if ((xtop = xml_new("config", NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) goto done; if ((xa = xml_new("operation", xbot)) == NULL) goto done; diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 15e0ce0a..8d42a328 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -165,7 +165,7 @@ expand_dbvar(void *h, if ((xtop = xml_new("config", NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) goto done; /* Special case for leafref. Detect leafref via Yang-type, * Get Yang path element, tentatively add the new syntax to the whole diff --git a/apps/netconf/netconf_plugin.c b/apps/netconf/netconf_plugin.c index cd2f9bc7..49847e50 100644 --- a/apps/netconf/netconf_plugin.c +++ b/apps/netconf/netconf_plugin.c @@ -196,27 +196,6 @@ catch: return -1; } -/*! Struct to carry info into and out of ys_find_rpc callback - */ -typedef struct { - char *name; /* name of rpc */ - yang_stmt *yrpc; /* matching yang statement */ -} find_rpc_arg; - -/*! Check yang rpc statement, return yang rpc statement if found - */ -static int -ys_find_rpc(yang_stmt *ys, - void *arg) -{ - find_rpc_arg *fra = (find_rpc_arg*)arg; - - if (strcmp(fra->name, ys->ys_argument) == 0){ - fra->yrpc = ys; - return 1; /* handled */ - } - return 0; -} /*! See if there is any callback registered for this tag * @@ -240,8 +219,7 @@ netconf_plugin_callbacks(clicon_handle h, yang_stmt *yinput; yang_stmt *youtput; cxobj *xoutput; - find_rpc_arg fra = {0,0}; - int ret; + cbuf *cb; if (deps != NULL){ nreg = deps; @@ -259,13 +237,17 @@ netconf_plugin_callbacks(clicon_handle h, clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; } + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "cbuf_new"); + goto done; + } + /* create absolute path */ + cprintf(cb, "/%s:%s", xml_namespace(xn), xml_name(xn)); /* Find yang rpc statement, return yang rpc statement if found */ - fra.name = xml_name(xn); - if ((ret = yang_apply((yang_node*)yspec, Y_RPC, ys_find_rpc, &fra)) < 0) + if (yang_abs_schema_nodeid(yspec, cbuf_get(cb), &yrpc) < 0) goto done; /* Check if found */ - if (ret == 1){ - yrpc = fra.yrpc; + if (yrpc != NULL){ if ((yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL)) != NULL){ xml_spec_set(xn, yinput); /* needed for xml_spec_populate */ if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yinput) < 0) @@ -300,6 +282,8 @@ netconf_plugin_callbacks(clicon_handle h, } retval = 0; done: + if (cb) + cbuf_free(cb); return retval; } diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 0c8d519c..2f19b221 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -77,7 +77,7 @@ resource ([RFC6415]) */ #define RESTCONF_API_ROOT "/restconf/" -/*! Generic REST method, GET, PUT, DELETE +/*! Generic REST method, GET, PUT, DELETE, etc * @param[in] h CLIXON handle * @param[in] r Fastcgi request handle * @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft]) @@ -120,6 +120,38 @@ api_data(clicon_handle h, return retval; } +/*! Operations REST method, POST + * @param[in] h CLIXON handle + * @param[in] r Fastcgi request handle + * @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft]) + * @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] dvec Stream input data + */ +static int +api_operations(clicon_handle h, + FCGX_Request *r, + char *path, + cvec *pcvec, + int pi, + cvec *qvec, + char *data) +{ + int retval = -1; + char *request_method; + + clicon_debug(1, "%s", __FUNCTION__); + request_method = FCGX_GetParam("REQUEST_METHOD", r->envp); + clicon_debug(1, "%s method:%s", __FUNCTION__, request_method); + if (strcmp(request_method, "POST")==0) + retval = api_operation_post(h, r, path, pcvec, pi, qvec, data); + else + retval = notfound(r); + return retval; +} + + /*! Process a FastCGI request * @param[in] r Fastcgi request handle */ @@ -176,6 +208,9 @@ request_process(clicon_handle h, if (strcmp(method, "data") == 0) /* restconf, skip /api/data */ retval = api_data(h, r, path, pcvec, 2, qvec, data); + else + if (strcmp(method, "operations") == 0) /* rpc */ + retval = api_operations(h, r, path, pcvec, 2, qvec, data); else if (strcmp(method, "test") == 0) retval = test(r, 0); else diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 9e2f4db5..35299ebf 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -354,7 +354,7 @@ api_data_post(clicon_handle h, if ((xtop = xml_new("config", NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) goto done; /* Parse input data as json into xml */ if (json_parse_str(data, &xdata) < 0){ @@ -462,7 +462,7 @@ api_data_put(clicon_handle h, if ((xtop = xml_new("config", NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) goto done; /* Parse input data as json into xml */ if (json_parse_str(data, &xdata) < 0){ @@ -581,7 +581,7 @@ api_data_delete(clicon_handle h, if ((xtop = xml_new("config", NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) goto done; if ((xa = xml_new("operation", xbot)) == NULL) goto done; @@ -606,7 +606,7 @@ api_data_delete(clicon_handle h, FCGX_FPrintF(r->out, "\r\n"); retval = 0; done: - if (cbx) + if (cbx) cbuf_free(cbx); if (xtop) xml_free(xtop); @@ -614,3 +614,116 @@ api_data_delete(clicon_handle h, return retval; } +/*! REST operation POST method + * @param[in] h CLIXON handle + * @param[in] r Fastcgi request handle + * @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 + * @note We map post to edit-config create. + + POST {+restconf}/operations/ + + + */ +int +api_operation_post(clicon_handle h, + FCGX_Request *r, + char *path, + cvec *pcvec, + int pi, + cvec *qvec, + char *data) +{ + int retval = -1; + int i; + char *oppath = path; + yang_stmt *yrpc = NULL; + yang_spec *yspec; + yang_stmt *yinput; + cxobj *xdata = NULL; + cxobj *xret = NULL; + cbuf *cbx = NULL; + cxobj *xtop = NULL; /* xpath root */ + cxobj *xbot = NULL; + yang_node *y = NULL; + cxobj *xinput; + cxobj *x; + cxobj **vec = NULL; + + clicon_debug(1, "%s json:\"%s\"", __FUNCTION__, data); + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_FATAL, 0, "No DB_SPEC"); + goto done; + } + for (i=0; i... + * eg + */ + /* Create config top-of-tree */ + if ((xtop = xml_new("rpc", NULL)) == NULL) + goto done; + xbot = xtop; + if (api_path2xml(oppath, yspec, xtop, 1, &xbot, &y) < 0) + goto done; + if (data){ + /* Parse input data as json into xml */ + if (json_parse_str(data, &xdata) < 0){ + clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data); + goto done; + } + /* xdata should have format */ + if ((xinput = xpath_first(xdata, "/input")) != NULL){ + /* Add all input under path */ + x = NULL; + while ((x = xml_child_each(xinput, x, -1)) != NULL) + if (xml_addsub(xbot, x) < 0) + goto done; + if ((yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL)) != NULL){ + xml_spec_set(xinput, yinput); /* needed for xml_spec_populate */ + if (xml_apply(xinput, CX_ELMNT, xml_spec_populate, yinput) < 0) + goto done; + if (xml_apply(xinput, CX_ELMNT, + (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) + goto done; + if (xml_yang_validate_add(xinput, NULL) < 0) + goto done; + } + } + } + if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) + goto done; + if ((cbx = cbuf_new()) == NULL) + goto done; + /* Sanity check of outgoing XML */ + FCGX_SetExitStatus(200, r->out); /* OK */ + FCGX_FPrintF(r->out, "Content-Type: application/yang.data+json\r\n"); + FCGX_FPrintF(r->out, "\r\n"); + vec = xml_childvec_get(xret); + if (xml2json_cbuf_vec(cbx, vec, xml_child_nr(xret), 0) < 0) + goto done; + clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx)); + FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); + FCGX_FPrintF(r->out, "\r\n\r\n"); + + retval = 0; + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (xdata) + xml_free(xdata); + if (xret) + xml_free(xret); + if (cbx) + cbuf_free(cbx); + return retval; +} diff --git a/apps/restconf/restconf_methods.h b/apps/restconf/restconf_methods.h index f6946f8a..661e01ba 100644 --- a/apps/restconf/restconf_methods.h +++ b/apps/restconf/restconf_methods.h @@ -60,4 +60,8 @@ int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path, cvec *qvec, char *data); int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi); +int api_operation_post(clicon_handle h, FCGX_Request *r, + char *path, + cvec *pcvec, int pi, cvec *qvec, char *data); + #endif /* _RESTCONF_METHODS_H_ */ diff --git a/datastore/keyvalue/clixon_keyvalue.c b/datastore/keyvalue/clixon_keyvalue.c index 3fb090c0..0c1ff566 100644 --- a/datastore/keyvalue/clixon_keyvalue.c +++ b/datastore/keyvalue/clixon_keyvalue.c @@ -321,7 +321,7 @@ get(char *dbname, restval++; } if (i == 1){ /* spec->module->node */ - if ((y = yang_find_topnode(ys, name)) == NULL){ + if ((y = yang_find_topnode(ys, name, 0)) == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", name); goto done; } @@ -808,7 +808,7 @@ kv_put(xmldb_handle xh, } // clicon_log(LOG_WARNING, "%s", __FUNCTION__); while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){ - if ((ys = yang_find_topnode(yspec, xml_name(x))) == NULL){ + if ((ys = yang_find_topnode(yspec, xml_name(x), 0)) == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", xml_name(x)); goto done; } diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index 386be2c9..d255d69f 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -663,7 +663,7 @@ text_modify_top(cxobj *x0, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); /* Get yang spec of the child */ - if ((yc = yang_find_topnode(yspec, x1cname)) == NULL){ + if ((yc = yang_find_topnode(yspec, x1cname, 0)) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; } diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index bfd27302..1cff0413 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -38,16 +38,6 @@ #ifndef _CLIXON_XML_MAP_H_ #define _CLIXON_XML_MAP_H_ -/* - * lvmap_xml op codes - */ -enum { - LVXML, /* a.b{x=1} -> 1 */ - LVXML_VAL, /* a.b{x=1} -> 1 */ - LVXML_VECVAL, /* key: a.b.0{x=1} -> 1 och */ - LVXML_VECVAL2, /* key: a.b.0{x=1} -> 1 och */ -}; - /* * Prototypes */ @@ -73,7 +63,8 @@ int xml_non_config_data(cxobj *xt, void *arg); int xml_spec_populate(cxobj *x, void *arg); int api_path2xpath_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath); int api_path2xpath(yang_spec *yspec, char *api_path, cbuf *xpath); -int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop, cxobj **xpathp, yang_node **ypathp); +int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop, + int schemanode, cxobj **xpathp, yang_node **ypathp); int xml_merge(cxobj *x0, cxobj *x1, yang_spec *yspec); int yang_enum_int_value(cxobj *node, int32_t *val); diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 9593181c..d62cfabe 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -133,6 +133,7 @@ enum rfc_6020{ typedef struct yang_stmt yang_stmt; /* forward */ /*! Yang type cache. Yang type statements can cache all typedef info here + * @note unions not cached */ struct yang_type_cache{ int yc_options; @@ -158,7 +159,7 @@ struct yang_stmt{ leaf, leaf-list, mandatory, fraction-digits */ cvec *ys_cvec; /* List of stmt-specific variables Y_RANGE: range_min, range_max */ - yang_type_cache *ys_typecache; /* If ys_keyword==Y_TYPE, cache all typedef data */ + yang_type_cache *ys_typecache; /* If ys_keyword==Y_TYPE, cache all typedef data except unions */ }; @@ -205,7 +206,8 @@ yang_spec *ys_spec(yang_stmt *ys); yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix); yang_stmt *yang_find(yang_node *yn, int keyword, char *argument); yang_stmt *yang_find_datanode(yang_node *yn, char *argument); -yang_stmt *yang_find_topnode(yang_spec *ysp, char *name); +yang_stmt *yang_find_schemanode(yang_node *yn, char *argument); +yang_stmt *yang_find_topnode(yang_spec *ysp, char *name, int schemanode); int yang_print(FILE *f, yang_node *yn); int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal); diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 355e2834..6416e342 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -974,7 +974,10 @@ clicon_xml2cbuf(cbuf *cb, } if (prettyprint && xml_body(x)==NULL) cprintf(cb, "%*s", level*XML_INDENT, ""); - cprintf(cb, "", name); + cprintf(cb, "", name); } if (prettyprint) cprintf(cb, "\n"); diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 449adc23..007c68db 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -644,7 +644,7 @@ xml_diff1(yang_stmt *ys, while ((x1 = xml_child_each(xt1, x1, CX_ELMNT)) != NULL){ name = xml_name(x1); if (ys->ys_keyword == Y_SPEC) - y = yang_find_topnode((yang_spec*)ys, name); + y = yang_find_topnode((yang_spec*)ys, name, 0); else y = yang_find_datanode((yang_node*)ys, name); if (y == NULL){ @@ -754,7 +754,7 @@ xml_diff1(yang_stmt *ys, while ((x2 = xml_child_each(xt2, x2, CX_ELMNT)) != NULL){ name = xml_name(x2); if (ys->ys_keyword == Y_SPEC) - y = yang_find_topnode((yang_spec*)ys, name); + y = yang_find_topnode((yang_spec*)ys, name, 0); else y = yang_find_datanode((yang_node*)ys, name); if (y == NULL){ @@ -1482,7 +1482,7 @@ xml_spec_populate(cxobj *x, (yp = xml_spec(xp)) != NULL) y = yang_find_datanode((yang_node*)yp, xml_name(x)); else - y = yang_find_topnode(yspec, name); /* still NULL for config */ + y = yang_find_topnode(yspec, name, 0); /* still NULL for config */ #ifdef XXX_OBSOLETE /* Add validate elsewhere */ if (y==NULL){ clicon_err(OE_XML, EBADF, "yang spec not found for xml node '%s' xml parent name: '%s' yangspec:'%s']", @@ -1544,7 +1544,7 @@ api_path2xpath_cvv(yang_spec *yspec, clicon_debug(1, "[%d] cvname:%s", i, name); clicon_debug(1, "cv2str%d", cv2str(cv, NULL, 0)); if (i == offset){ - if ((y = yang_find_topnode(yspec, name)) == NULL){ + if ((y = yang_find_topnode(yspec, name, 0)) == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", name); goto done; } @@ -1635,17 +1635,19 @@ api_path2xpath(yang_spec *yspec, * @param[in] nvec Length of vec * @param[in] x0 Xpath tree so far * @param[in] y0 Yang spec for x0 + * @param[in] schemanode If set use schema nodes otherwise data nodes. * @param[out] xpathp Resulting xml tree * @param[out] ypathp Yang spec matching xpathp * @see api_path2xml */ static int -api_path2xml_vec(char **vec, - int nvec, - cxobj *x0, - yang_node *y0, - cxobj **xpathp, - yang_node **ypathp) +api_path2xml_vec(char **vec, + int nvec, + cxobj *x0, + yang_node *y0, + int schemanode, + cxobj **xpathp, + yang_node **ypathp) { int retval = -1; int j; @@ -1663,6 +1665,7 @@ api_path2xml_vec(char **vec, int nvalvec; cxobj *x = NULL; yang_stmt *y = NULL; + char *local; if ((name = vec[0]) == NULL){ if (xpathp) @@ -1678,10 +1681,21 @@ api_path2xml_vec(char **vec, if (percent_decode(restval_enc, &restval) < 0) goto done; } - if (y0->yn_keyword == Y_SPEC) /* top-node */ - y = yang_find_topnode((yang_spec*)y0, name); - else - y = yang_find_datanode((yang_node*)y0, name); + /* Split into prefix and localname, ignore prefix for now */ + if ((local = index(name, ':')) != NULL){ + *local = '\0'; + local++; + name = local; + } + if (y0->yn_keyword == Y_SPEC){ /* top-node */ + clicon_debug(1, "%s 1 %s", __FUNCTION__, name); + y = yang_find_topnode((yang_spec*)y0, name, schemanode); + } + else { + clicon_debug(1, "%s 2 %s", __FUNCTION__, name); + y = schemanode?yang_find_schemanode((yang_node*)y0, name): + yang_find_datanode((yang_node*)y0, name); + } if (y == NULL){ clicon_err(OE_YANG, errno, "No yang node found: %s", name); goto done; @@ -1756,6 +1770,7 @@ api_path2xml_vec(char **vec, } if (api_path2xml_vec(vec+1, nvec-1, x, (yang_node*)y, + schemanode, xpathp, ypathp) < 0) goto done; retval = 0; @@ -1769,14 +1784,17 @@ api_path2xml_vec(char **vec, /*! Create xml tree from api-path * @param[in] api_path API-path as defined in RFC 8040 + * @param[in] yspec Yang spec + * @param[in] schemanode If set use schema nodes otherwise data nodes. * @param[out] xpathp Resulting xml tree * @param[out] ypathp Yang spec matching xpathp * @see api_path2xml_vec */ int -api_path2xml(char *api_path, +api_path2xml(char *api_path, yang_spec *yspec, cxobj *xpath, + int schemanode, cxobj **xpathp, yang_node **ypathp) { @@ -1800,7 +1818,7 @@ api_path2xml(char *api_path, } nvec--; /* NULL-terminated */ if (api_path2xml_vec(vec+1, nvec, - xpath, (yang_node*)yspec, + xpath, (yang_node*)yspec, schemanode, xpathp, ypathp) < 0) goto done; retval = 0; @@ -1984,7 +2002,7 @@ xml_merge(cxobj *x0, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); /* Get yang spec of the child */ - if ((yc = yang_find_topnode(yspec, x1cname)) == NULL){ + if ((yc = yang_find_topnode(yspec, x1cname, 0)) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; } @@ -2050,3 +2068,4 @@ yang_enum_int_value(cxobj *node, done: return retval; } + diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 5e490e08..676b1c42 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -453,18 +453,68 @@ yang_find_datanode(yang_node *yn, return ysmatch; } -/*! Find 'top-node', eg first data node in all (sub)modules in a yang spec +/*! Find child schema node with matching argument (container, leaf, etc) + * @note XXX unify code with yang_find_datanode? + * @see yang_find_datanode + */ +yang_stmt * +yang_find_schemanode(yang_node *yn, + char *argument) +{ + yang_stmt *ys = NULL; + yang_stmt *yc = NULL; + yang_stmt *ysmatch = NULL; + int i, j; + + for (i=0; iyn_len; i++){ + ys = yn->yn_stmt[i]; + if (ys->ys_keyword == Y_CHOICE){ /* Look for its children */ + for (j=0; jys_len; j++){ + yc = ys->ys_stmt[j]; + if (yc->ys_keyword == Y_CASE) /* Look for its children */ + ysmatch = yang_find_schemanode((yang_node*)yc, argument); + else + if (yang_schemanode(yc)){ + if (argument == NULL) + ysmatch = yc; + else + if (yc->ys_argument && strcmp(argument, yc->ys_argument) == 0) + ysmatch = yc; + } + if (ysmatch) + goto match; + } + } /* Y_CHOICE */ + else + if (yang_schemanode(ys)){ + if (argument == NULL) + ysmatch = ys; + else + if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) + ysmatch = ys; + if (ysmatch) + goto match; + } + } + match: + return ysmatch; +} + + +/*! Find first matching data node in all (sub)modules in a yang spec * * @param[in] ysp Yang specification - * @param[in] name if NULL, match any(first) argument. XXX is that really a case? + * @param[in] name if NULL, match any(first) argument. XXX is that really a case? + * @param[in] schemanode If set look for schema nodes, otherwise only data nodes * A yang specification has modules as children which in turn can have * syntax-nodes as children. This function goes through all the modules to - * look for syntax-nodes. Note that if a child to a module is a choice, + * look for nodes. Note that if a child to a module is a choice, * the search is made recursively made to the choice's children. */ yang_stmt * yang_find_topnode(yang_spec *ysp, - char *name) + char *name, + int schemanode) { yang_stmt *ys = NULL; yang_stmt *yc = NULL; @@ -472,12 +522,43 @@ yang_find_topnode(yang_spec *ysp, for (i=0; iyp_len; i++){ ys = ysp->yp_stmt[i]; - if ((yc = yang_find_datanode((yang_node*)ys, name)) != NULL) - return yc; + if (schemanode){ + if ((yc = yang_find_schemanode((yang_node*)ys, name)) != NULL) + return yc; + } + else + if ((yc = yang_find_datanode((yang_node*)ys, name)) != NULL) + return yc; } return NULL; } +/*! Given a yang statement, find the prefix associated to this module + * @param[in] ys Yang statement + * @retval NULL Not found + * @retval prefix Prefix as char* pointer into yang tree + */ +char * +yang_find_myprefix(yang_stmt *ys) +{ + yang_stmt *ymod; /* My module */ + yang_stmt *yprefix; + char *prefix = NULL; + + if ((ymod = ys_module(ys)) == NULL){ + clicon_err(OE_YANG, 0, "My yang module not found"); + goto done; + } + if ((yprefix = yang_find((yang_node*)ymod, Y_PREFIX, NULL)) == NULL){ + clicon_err(OE_YANG, 0, "No prefix in my module"); + goto done; + } + prefix = yprefix->ys_argument; + done: + return prefix; +} + + /*! Reset flag in complete tree, arg contains flag */ static int ys_flag_reset(yang_stmt *ys, @@ -505,8 +586,13 @@ ys_module(yang_stmt *ys) { yang_node *yn; +#if 1 + if (ys==NULL || ys->ys_keyword==Y_SPEC) + return NULL; +#else if (ys==NULL || ys->ys_keyword==Y_SPEC) return ys; +#endif while (ys != NULL && ys->ys_keyword != Y_MODULE && ys->ys_keyword != Y_SUBMODULE){ yn = ys->ys_parent; /* Some extra stuff to ensure ys is a stmt */ @@ -572,6 +658,7 @@ ytype_prefix(yang_stmt *ys) } + /*! Given a yang statement and a prefix, return yang module to that prefix * Note, not the other module but the proxy import statement only * @param[in] ys A yang statement @@ -588,22 +675,25 @@ yang_find_module_by_prefix(yang_stmt *ys, yang_stmt *my_ymod; yang_stmt *ymod = NULL; yang_spec *yspec; + char *myprefix; + if ((yspec = ys_spec(ys)) == NULL){ + clicon_err(OE_YANG, 0, "My yang spec not found"); + goto done; + } + myprefix = yang_find_myprefix(ys); if ((my_ymod = ys_module(ys)) == NULL){ clicon_err(OE_YANG, 0, "My yang module not found"); goto done; } - if ((yspec = ys_spec(my_ymod)) == NULL){ - clicon_err(OE_YANG, 0, "My yang spec not found"); - goto done; - } +#if 0 if (my_ymod->ys_keyword != Y_MODULE && my_ymod->ys_keyword != Y_SUBMODULE){ clicon_err(OE_YANG, 0, "%s not module or sub-module", my_ymod->ys_argument); goto done; } - if ((yprefix = yang_find((yang_node*)my_ymod, Y_PREFIX, NULL)) != NULL && - strcmp(yprefix->ys_argument, prefix) == 0){ +#endif + if (strcmp(myprefix, prefix) == 0){ ymod = my_ymod; goto done; } @@ -1730,6 +1820,7 @@ yang_abs_schema_nodeid(yang_spec *yspec, yang_stmt *ymod; char *id; char *prefix = NULL; + yang_stmt *yprefix; /* check absolute schema_nodeid */ @@ -1765,9 +1856,16 @@ yang_abs_schema_nodeid(yang_spec *yspec, break; } } - if (ymod == NULL){ - clicon_err(OE_YANG, 0, "Module with prefix %s not found", prefix); - goto done; + if (ymod == NULL){ /* Try with topnode */ + yang_stmt *ys; + if ((ys = yang_find_topnode(yspec, id, 1)) == NULL){ + clicon_err(OE_YANG, 0, "Module with id:%s:%s not found", prefix,id); + goto done; + } + if ((ymod = ys_module(ys)) == NULL){ + clicon_err(OE_YANG, 0, "Module with id:%s:%s not found2", prefix,id); + goto done; + } } if (schema_nodeid_vec((yang_node*)ymod, vec+1, nvec-1, yres) < 0) goto done; diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c index 72a2f83c..37393909 100644 --- a/lib/src/clixon_yang_type.c +++ b/lib/src/clixon_yang_type.c @@ -107,6 +107,8 @@ yang_builtin(char *type) return 0; } +/*! Set type cache for yang type + */ int yang_type_cache_set(yang_type_cache **ycache0, yang_stmt *resolved, @@ -142,7 +144,6 @@ yang_type_cache_set(yang_type_cache **ycache0, } ycache->yc_fraction = fraction; retval = 0; - done: return retval; } @@ -209,6 +210,7 @@ yang_type_cache_free(yang_type_cache *ycache) * @param[in] ys This is a type statement * @param[in] arg Not used * Typically only called once when loading te yang type system. + * @note unions not cached */ int ys_resolve_type(yang_stmt *ys, @@ -226,13 +228,16 @@ ys_resolve_type(yang_stmt *ys, if (yang_type_resolve((yang_stmt*)ys->ys_parent, ys, &resolved, &options, &mincv, &maxcv, &pattern, &fraction) < 0) goto done; - /* skip unions since they may have different sets of options, mincv, etc */ + if (resolved && strcmp(resolved->ys_argument, "union")==0) - ; + ; + /* skip unions since they may have different sets of options, mincv, etc + * You would have to resolve all sub-types also recursively + */ else - if (yang_type_cache_set(&ys->ys_typecache, - resolved, options, mincv, maxcv, pattern, fraction) < 0) - goto done; + if (yang_type_cache_set(&ys->ys_typecache, + resolved, options, mincv, maxcv, pattern, fraction) < 0) + goto done; retval = 0; done: return retval; diff --git a/test/test_netconf.sh b/test/test_netconf.sh index c1826ab8..39fd6772 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -126,6 +126,9 @@ new "netconf check empty startup" expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" new "netconf rpc" +expecteof "$clixon_netconf -qf $clixon_cf" "ipv4ipv4]]>]]>" "^ipv4" + +new "netconf rpc w/o namespace" expecteof "$clixon_netconf -qf $clixon_cf" "ipv4ipv4]]>]]>" "^ipv4" new "netconf subscription" diff --git a/test/test_restconf.sh b/test/test_restconf.sh index cd4df191..b9e9e236 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -35,53 +35,55 @@ expectfn "curl -sS -I http://localhost/restconf/data" "Content-Type: application new "restconf get empty config" expectfn "curl -sSG http://localhost/restconf/data" "^null $" -# -new "Add subtree to datastore using POST" +new "restconf Add subtree to datastore using POST" expectfn 'curl -sS -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}}} http://localhost/restconf/data' "" -new "Check interfaces eth/0/0 added" +new "restconf Check interfaces eth/0/0 added" expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}} $' -new "delete interfaces" +new "restconf delete interfaces" expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces' "" -new "Check empty config" +new "restconf Check empty config" expectfn "curl -sSG http://localhost/restconf/data" "^null $" -new "Add interfaces subtree eth/0/0 using POST" +new "restconf Add interfaces subtree eth/0/0 using POST" expectfn 'curl -sS -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces' "" -new "Check eth/0/0 added" +new "restconf Check eth/0/0 added" expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}} $' -new "Re-post eth/0/0 which should generate error" +new "restconf Re-post eth/0/0 which should generate error" expectfn 'curl -sS -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces' "Data resource already exists" new "Add leaf description using POST" expectfn 'curl -sS -X POST -d {"description":"The-first-interface"} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" -new "Check description added" +new "restconf Check description added" expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","description": "The-first-interface","type": "eth","enabled": "true"}} $' -new "delete eth/0/0" +new "restconf delete eth/0/0" expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" -#new "Check deleted eth/0/0" -#expectfn 'curl -sS -G http://localhost/restconf/data' '{"interfaces": null} -#$' +new "Check deleted eth/0/0" +expectfn 'curl -sS -G http://localhost/restconf/data' "^null $" -new "Re-Delete eth/0/0 using none should generate error" +new "restconf Re-Delete eth/0/0 using none should generate error" expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "Not Found" -new "Add subtree eth/0/0 using PUT" +new "restconf Add subtree eth/0/0 using PUT" expectfn 'curl -sS -X PUT -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" +new "restconf get subtree" expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}} $' +new "restconf rpc using POST (wrong output encoding)" +expectfn 'curl -sS -X POST -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/rt:fib-route' '{"rpc-reply": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"}}}} $' + new "Kill restconf daemon" sudo pkill -u www-data clixon_restconf