diff --git a/CHANGELOG.md b/CHANGELOG.md index 09c863b3..a300d032 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,13 @@ # Clixon Changelog ## 3.6.0 (Upcoming) -### Major changes: -### Minor changes: -### Corrected Bugs -* Translate xml->json \n correctly + ### Major changes: ### Minor changes: +* yang_find_topnode() and api_path2xml() schemanode parameter replaced with yang_class. Replace as follows: 0 -> YC_DATANODE, 1 -> YC_SCHEMANODE +* xml2json: include prefix in translation, so is translated to {"a:b" ..} * Use instead of when save/load configuration to file. This enables saved files to be used as datastore without any editing. Thanks Matt. @@ -21,6 +20,7 @@ enables saved files to be used as datastore without any editing. Thanks Matt. * Added cli_show_version() ### Corrected Bugs +* Translate xml->json \n correctly * Fix issue: https://github.com/clicon/clixon/issues/15 Replace whole config ## 3.5.0 (12 February 2018) diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 0a869980..a4ec0130 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)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) goto done; if ((xa = xml_new("operation", xbot, NULL)) == NULL) goto done; diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 13f58339..d3569aa6 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -171,7 +171,7 @@ expand_dbvar(void *h, /* This is primarily to get "y", * xpath2xml would have worked!! */ - if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &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/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 5ae99543..a2be4db7 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -131,11 +131,12 @@ api_data(clicon_handle h, /*! 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] 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 + * @param[in] data Stream input data + * @param[in] username Authenticated user */ static int api_operations(clicon_handle h, @@ -154,16 +155,42 @@ api_operations(clicon_handle h, request_method = FCGX_GetParam("REQUEST_METHOD", r->envp); clicon_debug(1, "%s method:%s", __FUNCTION__, request_method); if (strcmp(request_method, "GET")==0) - retval = api_operation_get(h, r, path, pcvec, pi, qvec, data, username); + retval = api_operations_get(h, r, path, pcvec, pi, qvec, data, username); else if (strcmp(request_method, "POST")==0) - retval = api_operation_post(h, r, path, pcvec, pi, qvec, data, username); + retval = api_operations_post(h, r, path, pcvec, pi, qvec, data, username); else retval = notfound(r); return retval; } +/*! Determine the root of the RESTCONF API + * @param[in] h Clicon handle + * @param[in] r Fastcgi request handle + * @note Hardcoded to "/restconf" + * Return see RFC8040 3.1 and RFC7320 + * In line with the best practices defined by [RFC7320], RESTCONF + * enables deployments to specify where the RESTCONF API is located. + */ +static int +api_well_known(clicon_handle h, + FCGX_Request *r) +{ + clicon_debug(1, "%s", __FUNCTION__); + FCGX_FPrintF(r->out, "Content-Type: application/xrd+xml\r\n"); + FCGX_FPrintF(r->out, "\r\n"); + FCGX_SetExitStatus(200, r->out); /* OK */ + FCGX_FPrintF(r->out, "\r\n"); + FCGX_FPrintF(r->out, " \r\n"); + FCGX_FPrintF(r->out, "\r\n"); + + return 0; +} + /*! Retrieve the Top-Level API Resource + * @param[in] h Clicon handle + * @param[in] r Fastcgi request handle * @note Only returns null for operations and data,... + * See RFC8040 3.3 */ static int api_root(clicon_handle h, @@ -181,9 +208,11 @@ api_root(clicon_handle h, media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); if (strcmp(media_accept, "application/yang-data+xml")==0) use_xml++; + clicon_debug(1, "%s use-xml:%d media-accept:%s", __FUNCTION__, use_xml, media_accept); FCGX_SetExitStatus(200, r->out); /* OK */ FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); FCGX_FPrintF(r->out, "\r\n"); + if (xml_parse_string("2016-06-21", NULL, &xt) < 0) goto done; if ((cb = cbuf_new()) == NULL){ @@ -298,6 +327,8 @@ api_restconf(clicon_handle h, retval = notfound(r); goto done; } + test(r, 1); + if (pn == 2){ retval = api_root(h, r); goto done; @@ -319,7 +350,6 @@ api_restconf(clicon_handle h, if (str2cvec(data, '&', '=', &dvec) < 0) goto done; - test(r, 1); /* If present, check credentials. See "plugin_credentials" in plugin * See RFC 8040 section 2.5 */ @@ -367,23 +397,6 @@ api_restconf(clicon_handle h, return retval; } -/*! Process a FastCGI request - * @param[in] r Fastcgi request handle - */ -static int -api_well_known(clicon_handle h, - FCGX_Request *r) -{ - clicon_debug(1, "%s", __FUNCTION__); - FCGX_FPrintF(r->out, "Content-Type: application/xrd+xml\r\n"); - FCGX_FPrintF(r->out, "\r\n"); - FCGX_SetExitStatus(200, r->out); /* OK */ - FCGX_FPrintF(r->out, "\r\n"); - FCGX_FPrintF(r->out, " \r\n"); - FCGX_FPrintF(r->out, "\r\n"); - - return 0; -} static int restconf_terminate(clicon_handle h) @@ -547,7 +560,7 @@ main(int argc, if (strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)) == 0) api_restconf(h, r); /* This is the function */ else if (strncmp(path, RESTCONF_WELL_KNOWN, strlen(RESTCONF_WELL_KNOWN)) == 0) { - api_well_known(h, r); /* This is the function */ + api_well_known(h, r); /* */ } else{ clicon_debug(1, "top-level %s not found", path); diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 992a8662..1bef74fc 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -195,6 +195,7 @@ api_data_get_err(clicon_handle h, * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] username Authenticated user * @param[in] head If 1 is HEAD, otherwise GET * @code * curl -G http://localhost/restconf/data/interfaces/interface=eth0 @@ -332,6 +333,7 @@ api_data_get2(clicon_handle h, * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] username Authenticated user The HEAD method is sent by the client to retrieve just the header fields that would be returned for the comparable GET method, without the response message-body. @@ -355,6 +357,7 @@ api_data_head(clicon_handle h, * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] username Authenticated user * @code * curl -G http://localhost/restconf/data/interfaces/interface=eth0 * @endcode @@ -390,6 +393,8 @@ api_data_get(clicon_handle h, * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data + * @param[in] username Authenticated user + * @note restconf POST is mapped to edit-config create. POST: target resource type is datastore --> create a top-level resource @@ -461,7 +466,7 @@ api_data_post(clicon_handle h, goto done; } - if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) goto done; /* Parse input data as json or xml into xml */ if (parse_xml){ @@ -586,6 +591,7 @@ match_list_keys(yang_stmt *y, * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data + * @param[in] username Authenticated user * @note restconf PUT is mapped to edit-config replace. * @example curl -X PUT -d '{"enabled":"false"}' http://127.0.0.1/restconf/data/interfaces/interface=eth1 @@ -651,7 +657,7 @@ api_data_put(clicon_handle h, if (xml_value_set(xu, username) < 0) goto done; } - if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) goto done; /* Parse input data as json or xml into xml */ if (parse_xml){ @@ -749,6 +755,7 @@ api_data_put(clicon_handle h, * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data + * @param[in] username Authenticated user * Netconf: (nc:operation="merge") */ int @@ -769,6 +776,7 @@ api_data_patch(clicon_handle h, * @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] username Authenticated user * @param[in] pi Offset, where path starts * Example: * curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0 @@ -812,7 +820,7 @@ api_data_delete(clicon_handle h, if (xml_value_set(xu, username) < 0) goto done; } - if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) goto done; if ((xa = xml_new("operation", xbot, NULL)) == NULL) goto done; @@ -851,10 +859,27 @@ api_data_delete(clicon_handle h, return retval; } -/*! NYI +/*! GET restconf/operations resource + * @param[in] h Clixon handle + * @param[in] r Fastcgi request handle + * @param[in] 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 path starts + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] data Stream input data + * @param[in] username Authenticated user + * + * @code + * curl -G http://localhost/restconf/operations + * @endcode + * RFC8040 Sec 3.3.2: + * This optional resource is a container that provides access to the + * data-model-specific RPC operations supported by the server. The + * server MAY omit this resource if no data-model-specific RPC + * operations are advertised. */ int -api_operation_get(clicon_handle h, +api_operations_get(clicon_handle h, FCGX_Request *r, char *path, cvec *pcvec, @@ -863,23 +888,87 @@ api_operation_get(clicon_handle h, char *data, char *username) { - notimplemented(r); - return 0; + int retval = -1; + int pretty; + char *media_accept; + int use_xml = 0; /* By default use JSON */ + yang_spec *yspec; + yang_stmt *ym; + yang_stmt *yc; + yang_stmt *yprefix; + char *prefix; + cbuf *cbx = NULL; + cxobj *xt = NULL; + + clicon_debug(1, "%s", __FUNCTION__); + pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); + media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); + if (strcmp(media_accept, "application/yang-data+xml")==0) + use_xml++; + yspec = clicon_dbspec_yang(h); + if ((cbx = cbuf_new()) == NULL) + goto done; + cprintf(cbx, ""); + ym = NULL; + while ((ym = yn_each((yang_node*)yspec, ym)) != NULL) { + if ((yprefix = yang_find((yang_node*)ym, Y_PREFIX, NULL)) != NULL) + prefix = yprefix->ys_argument; + else + continue; + yc = NULL; + while ((yc = yn_each((yang_node*)ym, yc)) != NULL) { + if (yc->ys_keyword != Y_RPC) + continue; + cprintf(cbx, "<%s:%s />", prefix, yc->ys_argument); + } + } + cprintf(cbx, ""); + clicon_debug(1, "%s xml:%s", __FUNCTION__, cbuf_get(cbx)); + if (xml_parse_string(cbuf_get(cbx), yspec, &xt) < 0) + goto done; + if (xml_rootchild(xt, 0, &xt) < 0) + goto done; + cbuf_reset(cbx); /* reuse same cbuf */ + if (use_xml){ + if (clicon_xml2cbuf(cbx, xt, 0, pretty) < 0) /* Dont print top object? */ + goto done; + } + else{ + if (xml2json_cbuf(cbx, xt, pretty) < 0) + goto done; + } + clicon_debug(1, "%s ret:%s", __FUNCTION__, cbuf_get(cbx)); + FCGX_SetExitStatus(200, r->out); /* OK */ + FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); + FCGX_FPrintF(r->out, "\r\n"); + FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); + FCGX_FPrintF(r->out, "\r\n\r\n"); + // ok: + retval = 0; + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (cbx) + cbuf_free(cbx); + if (xt) + xml_free(xt); + return retval; } /*! REST operation POST method * @param[in] h CLIXON handle * @param[in] r Fastcgi request handle + * @param[in] 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] data Stream input data - * @note We map post to edit-config create. + * @param[in] username Authenticated user + * @note We map post to edit-config create. POST {+restconf}/operations/ */ int -api_operation_post(clicon_handle h, +api_operations_post(clicon_handle h, FCGX_Request *r, char *path, cvec *pcvec, @@ -956,7 +1045,7 @@ api_operation_post(clicon_handle h, } /* XXX: something strange for rpc user */ - if (api_path2xml(oppath, yspec, xtop, 1, &xbot, &y) < 0) + if (api_path2xml(oppath, yspec, xtop, YC_SCHEMANODE, &xbot, &y) < 0) goto done; #if 1 { diff --git a/apps/restconf/restconf_methods.h b/apps/restconf/restconf_methods.h index b50b91a6..246552b2 100644 --- a/apps/restconf/restconf_methods.h +++ b/apps/restconf/restconf_methods.h @@ -61,13 +61,13 @@ int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path, int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi, char *username); -int api_operation_get(clicon_handle h, FCGX_Request *r, - char *path, - cvec *pcvec, int pi, cvec *qvec, char *data, char *username); - -int api_operation_post(clicon_handle h, FCGX_Request *r, +int api_operations_get(clicon_handle h, FCGX_Request *r, char *path, - cvec *pcvec, int pi, cvec *qvec, char *data, - char *username); + cvec *pcvec, int pi, cvec *qvec, char *data, char *username); + +int api_operations_post(clicon_handle h, FCGX_Request *r, + char *path, + cvec *pcvec, int pi, cvec *qvec, char *data, + char *username); #endif /* _RESTCONF_METHODS_H_ */ diff --git a/datastore/keyvalue/clixon_keyvalue.c b/datastore/keyvalue/clixon_keyvalue.c index 018c86fd..c1ad1911 100644 --- a/datastore/keyvalue/clixon_keyvalue.c +++ b/datastore/keyvalue/clixon_keyvalue.c @@ -312,7 +312,7 @@ get(char *dbname, restval++; } if (i == 1){ /* spec->module->node */ - if ((y = yang_find_topnode(ys, name, 0)) == NULL){ + if ((y = yang_find_topnode(ys, name, YC_DATANODE)) == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", name); goto done; } @@ -799,7 +799,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), 0)) == NULL){ + if ((ys = yang_find_topnode(yspec, xml_name(x), YC_DATANODE)) == 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 776de8d7..b9eddc5a 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -792,7 +792,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, 0)) == NULL){ + if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == 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 6d3befc3..71cb0e46 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -64,7 +64,7 @@ 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, - int schemanode, cxobj **xpathp, yang_node **ypathp); + yang_class nodeclass, 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 c89a8f80..589bfb04 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -123,14 +123,45 @@ enum rfc_6020{ Y_SPEC /* XXX: NOTE NOT YANG STATEMENT, reserved for top level spec */ }; +/* Type used to group yang nodes used in some functions + * See RFC7950 Sec 3 + */ +enum yang_class{ + YC_NONE, /* Someting else,... */ + YC_DATANODE, /* See yang_datanode() */ + YC_DATADEFINITION, /* See yang_datadefinition() */ + YC_SCHEMANODE /* See yang_schemanode() */ +}; +typedef enum yang_class yang_class; + #define YANG_FLAG_MARK 0x01 /* Marker for dynamic algorithms, eg expand */ -/* Yang data node */ +/* Yang data node + * See RFC7950 Sec 3: + * o data node: A node in the schema tree that can be instantiated in a + * data tree. One of container, leaf, leaf-list, list, anydata, and + * anyxml. + */ #define yang_datanode(y) ((y)->ys_keyword == Y_CONTAINER || (y)->ys_keyword == Y_LEAF || (y)->ys_keyword == Y_LIST || (y)->ys_keyword == Y_LEAF_LIST || (y)->ys_keyword == Y_ANYXML) -/* Yang schema node */ +/* Yang data definition statement + * See RFC 7950 Sec 3: + * o data definition statement: A statement that defines new data + * nodes. One of "container", "leaf", "leaf-list", "list", "choice", + * "case", "augment", "uses", "anydata", and "anyxml". + */ +#define yang_datadefinition(y) (yang_datanode(y) || (y)->ys_keyword == Y_CHOICE || (y)->ys_keyword == Y_CASE || (y)->ys_keyword == Y_AUGMENT || (y)->ys_keyword == Y_USES) + + +/* Yang schema node . + * See RFC 7950 Sec 3: + * o schema node: A node in the schema tree. One of action, container, + * leaf, leaf-list, list, choice, case, rpc, input, output, + * notification, anydata, and anyxml. + */ #define yang_schemanode(y) (yang_datanode(y) || (y)->ys_keyword == Y_RPC || (y)->ys_keyword == Y_CHOICE || (y)->ys_keyword == Y_CASE || (y)->ys_keyword == Y_INPUT || (y)->ys_keyword == Y_OUTPUT || (y)->ys_keyword == Y_NOTIFICATION) + typedef struct yang_stmt yang_stmt; /* forward */ /*! Yang type cache. Yang type statements can cache all typedef info here @@ -217,7 +248,7 @@ 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_schemanode(yang_node *yn, char *argument); -yang_stmt *yang_find_topnode(yang_spec *ysp, char *name, int schemanode); +yang_stmt *yang_find_topnode(yang_spec *ysp, char *name, yang_class class); int yang_order(yang_stmt *y); 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_json.c b/lib/src/clixon_json.c index e9ac79b5..b14592cc 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -304,10 +304,12 @@ xml2json1_cbuf(cbuf *cb, break; } case NO_ARRAY: - if (!flat) - cprintf(cb, "%*s\"%s\": ", - pretty?(level*JSON_INDENT):0, "", - xml_name(x)); + if (!flat){ + cprintf(cb, "%*s\"", pretty?(level*JSON_INDENT):0, ""); + if (xml_namespace(x)) + cprintf(cb, "%s:", xml_namespace(x)); + cprintf(cb, "%s\": ", xml_name(x)); + } switch (childt){ case NULL_CHILD: cprintf(cb, "null"); @@ -323,9 +325,10 @@ xml2json1_cbuf(cbuf *cb, break; case FIRST_ARRAY: case SINGLE_ARRAY: - cprintf(cb, "%*s\"%s\": ", - pretty?(level*JSON_INDENT):0, "", - xml_name(x)); + cprintf(cb, "%*s\"", pretty?(level*JSON_INDENT):0, ""); + if (xml_namespace(x)) + cprintf(cb, "%s:", xml_namespace(x)); + cprintf(cb, "%s\": ", xml_name(x)); level++; cprintf(cb, "[%s%*s", pretty?"\n":"", diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 105b8661..5f2e6b7b 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -587,7 +587,7 @@ yang_next(yang_node *y, yang_stmt *ys; if (y->yn_keyword == Y_SPEC) - ys = yang_find_topnode((yang_spec*)y, name, 0); + ys = yang_find_topnode((yang_spec*)y, name, YC_DATANODE); else ys = yang_find_datanode(y, name); if (ys == NULL) @@ -1313,7 +1313,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, 0); /* still NULL for config */ + y = yang_find_topnode(yspec, name, YC_DATANODE); /* still NULL for config */ #endif if (y) xml_spec_set(x, y); @@ -1367,7 +1367,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, 0)) == NULL){ + if ((y = yang_find_topnode(yspec, name, YC_DATANODE)) == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", name); goto done; } @@ -1448,7 +1448,7 @@ 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[in] nodeclass Set to schema nodes, data nodes, etc * @param[out] xpathp Resulting xml tree * @param[out] ypathp Yang spec matching xpathp * @see api_path2xml @@ -1458,7 +1458,7 @@ api_path2xml_vec(char **vec, int nvec, cxobj *x0, yang_node *y0, - int schemanode, + yang_class nodeclass, cxobj **xpathp, yang_node **ypathp) { @@ -1500,10 +1500,10 @@ api_path2xml_vec(char **vec, name = local; } if (y0->yn_keyword == Y_SPEC){ /* top-node */ - y = yang_find_topnode((yang_spec*)y0, name, schemanode); + y = yang_find_topnode((yang_spec*)y0, name, nodeclass); } else { - y = schemanode?yang_find_schemanode((yang_node*)y0, name): + y = (nodeclass==YC_SCHEMANODE)?yang_find_schemanode((yang_node*)y0, name): yang_find_datanode((yang_node*)y0, name); } if (y == NULL){ @@ -1572,7 +1572,7 @@ api_path2xml_vec(char **vec, } if (api_path2xml_vec(vec+1, nvec-1, x, (yang_node*)y, - schemanode, + nodeclass, xpathp, ypathp) < 0) goto done; retval = 0; @@ -1588,7 +1588,7 @@ api_path2xml_vec(char **vec, * @param[in] api_path API-path as defined in RFC 8040 * @param[in] yspec Yang spec * @param[in,out] xtop Incoming XML tree - * @param[in] schemanode If set use schema nodes otherwise data nodes. + * @param[in] nodeclass Set to schema nodes, data nodes, etc * @param[out] xbotp Resulting xml tree (end of xpath) * @param[out] ybotp Yang spec matching xbotp * @example @@ -1605,7 +1605,7 @@ int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop, - int schemanode, + yang_class nodeclass, cxobj **xbotp, yang_node **ybotp) { @@ -1628,7 +1628,7 @@ api_path2xml(char *api_path, } nvec--; /* NULL-terminated */ if (api_path2xml_vec(vec+1, nvec, - xtop, (yang_node*)yspec, schemanode, + xtop, (yang_node*)yspec, nodeclass, xbotp, ybotp) < 0) goto done; retval = 0; @@ -1736,7 +1736,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, 0)) == NULL){ + if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; } diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index ed2e987e..0016a59b 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -93,7 +93,7 @@ xml_child_spec(char *name, if (xp && (yparent = xml_spec(xp)) != NULL) y = yang_find_datanode((yang_node*)yparent, name); else if (yspec) - y = yang_find_topnode(yspec, name, 0); /* still NULL for config */ + y = yang_find_topnode(yspec, name, YC_DATANODE); /* still NULL for config */ else y = NULL; *yresult = y; diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index ba3758b7..2ecae84b 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -402,6 +402,94 @@ yang_find(yang_node *yn, } return match ? ys : NULL; } +#ifdef NOTYET +/*! Prototype more generic than yang_find_datanode and yang_find_schemanode + */ +yang_stmt * +yang_find_class(yang_node *yn, + char *argument, + yang_class class) +{ + yang_stmt *ys = NULL; + yang_stmt *yc = NULL; + yang_stmt *ysmatch = NULL; + int i, j; + int ok; + + for (i=0; iyn_len; i++){ + ys = yn->yn_stmt[i]; + switch(class){ + case YC_NONE: + ok = 1; + break; + case YC_DATANODE: + ok = yang_datanode(ys); + break; + case YC_DATADEFINITION: + ok = yang_datadefinition(ys); + break; + case YC_SCHEMANODE: + ok = yang_schemanode(ys); + break; + } + if (!ok) + continue; + switch(class){ + case YC_NONE: + if (argument == NULL) + ysmatch = ys; + else + if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) + ysmatch = ys; + if (ysmatch) + goto match; + break; + case YC_DATANODE: + case YC_DATADEFINITION: + if (argument == NULL) + ysmatch = ys; + else + if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) + ysmatch = ys; + if (ysmatch) + goto match; + break; + case YC_SCHEMANODE: + 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_class((yang_node*)yc, argument, class); + 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 (argument == NULL) + ysmatch = ys; + else + if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) + ysmatch = ys; + if (ysmatch) + goto match; + + } + break; + } /* switch */ + } /* for */ + match: + return ysmatch; +} +#endif /* NOTYET */ /*! Find child data node with matching argument (container, leaf, etc) * @@ -455,6 +543,8 @@ yang_find_datanode(yang_node *yn, } /*! Find child schema node with matching argument (container, leaf, etc) + * @param[in] yn Yang node, current context node. + * @param[in] argument if NULL, match any(first) argument. * @note XXX unify code with yang_find_datanode? * @see yang_find_datanode */ @@ -505,7 +595,7 @@ yang_find_schemanode(yang_node *yn, /*! 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] argument 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 @@ -514,8 +604,8 @@ yang_find_schemanode(yang_node *yn, */ yang_stmt * yang_find_topnode(yang_spec *ysp, - char *name, - int schemanode) + char *argument, + yang_class class) { yang_stmt *ys = NULL; yang_stmt *yc = NULL; @@ -523,13 +613,22 @@ yang_find_topnode(yang_spec *ysp, for (i=0; iyp_len; i++){ ys = ysp->yp_stmt[i]; - if (schemanode){ - if ((yc = yang_find_schemanode((yang_node*)ys, name)) != NULL) + switch (class){ + case YC_NONE: + if ((yc = yang_find((yang_node*)ys, 0, argument)) != NULL) return yc; + break; + case YC_DATANODE: + if ((yc = yang_find_datanode((yang_node*)ys, argument)) != NULL) + return yc; + break; + case YC_SCHEMANODE: + if ((yc = yang_find_schemanode((yang_node*)ys, argument)) != NULL) + return yc; + break; + case YC_DATADEFINITION: + break; /* nyi */ } - else - if ((yc = yang_find_datanode((yang_node*)ys, name)) != NULL) - return yc; } return NULL; } @@ -1929,7 +2028,7 @@ yang_abs_schema_nodeid(yang_spec *yspec, } if (ymod == NULL){ /* Try with topnode */ yang_stmt *ys; - if ((ys = yang_find_topnode(yspec, id, 1)) == NULL){ + if ((ys = yang_find_topnode(yspec, id, YC_SCHEMANODE)) == NULL){ clicon_err(OE_YANG, 0, "Module with id:%s:%s not found", prefix,id); goto done; } diff --git a/test/test_restconf.sh b/test/test_restconf.sh index c1a25263..61073308 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -78,22 +78,49 @@ sleep 1 new "restconf tests" -new "restconf options" -expectfn "curl -i -sS -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE" - -new "restconf head" -expectfn "curl -s -I http://localhost/restconf/data" "HTTP/1.1 200 OK" -#Content-Type: application/yang-data+json" - -new "restconf root discovery" +new "restconf root discovery. RFC 8040 3.1 (xml+xrd)" expectfn "curl -s -X GET http://localhost/.well-known/host-meta" "" -new "restconf get restconf json" +new "restconf get restconf resource. RFC 8040 3.3 (json)" expectfn "curl -sG http://localhost/restconf" '{"data": null,"operations": null,"yang-library-version": "2016-06-21"}}' -new "restconf get restconf/yang-library-version json" +new "restconf get restconf resource. RFC 8040 3.3 (xml)" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf) +expect="2016-06-21" +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + +new "restconf get restconf/operations. RFC8040 3.3.2" +expectfn "curl -sG http://localhost/restconf/operations" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"rt:fib-route": null,"rt:route-count": null}}' + +new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) +expect="" +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + +new "restconf get restconf/yang-library-version. RFC8040 3.3.3" expectfn "curl -sG http://localhost/restconf/yang-library-version" '{"yang-library-version": "2016-06-21"}' +new "restconf get restconf/yang-library-version. RFC8040 3.3.3 (xml)" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/yang-library-version) +expect="2016-06-21" +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + +new "restconf options. RFC 8040 4.1" +expectfn "curl -i -s -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE" + +new "restconf head. RFC 8040 4.2" +expectfn "curl -s -I http://localhost/restconf/data" "HTTP/1.1 200 OK" +#Content-Type: application/yang-data+json" + new "restconf empty rpc" expectfn 'curl -s -X POST -d {"input":{"name":""}} http://localhost/restconf/operations/ex:empty' '{"output": null}' @@ -101,6 +128,7 @@ new "restconf get empty config + state json" expectfn "curl -sSG http://localhost/restconf/data" "{\"data\": $state}" new "restconf get empty config + state xml" +# Cant get shell macros to work, inline matching from lib.sh ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data) expect="eth0eth42" match=`echo $ret | grep -EZo "$expect"` diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index 5e73ac31..cdd2d309 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -65,7 +65,6 @@ sleep 1 new "restconf tests" - new "restconf POST initial tree" expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' ""