diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index bfe6fbeb..281ce713 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -1530,6 +1530,7 @@ from_client_get_pageable_list(clicon_handle h, clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } + /* This uses xpath. Maybe count/limit should use parameters */ cprintf(cb, "%s", xpath); if (where) cprintf(cb, "[%s]", where); diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index fc75c9a1..277703d6 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -1312,3 +1312,76 @@ cli_help(clicon_handle h, cvec *vars, cvec *argv) return cligen_help(ch, stdout, pt); } +/*! Show pagination/collection + * XXX: This is hardcoded only for test_pagination.sh + * @param[in] h Clicon handle + * @param[in] cvv Vector of cli string and instantiated variables + * @param[in] argv Vector. Format: + */ +int +cli_pagination(clicon_handle h, cvec *vars, cvec *argv) +{ + int retval = -1; + cbuf *cb = NULL; + char *xpath = NULL; + char *prefix = NULL; + char *namespace = NULL; + cxobj *xret = NULL; + cxobj *xerr; + cvec *nsc = NULL; + char *formatstr; + enum format_enum format; + cxobj *xc; + + if (cvec_len(argv) != 4){ + clicon_err(OE_PLUGIN, 0, "Expected usage: "); + goto done; + } + xpath = cvec_i_str(argv, 0); + prefix = cvec_i_str(argv, 1); + namespace = cvec_i_str(argv, 2); + formatstr = cv_string_get(cvec_i(argv, 3)); /* Fourthformat: output format */ + if ((int)(format = format_str2int(formatstr)) < 0){ + clicon_err(OE_PLUGIN, 0, "Not valid format: %s", formatstr); + goto done; + } + if ((nsc = xml_nsctx_init(prefix, namespace)) == NULL) + goto done; + if (clicon_rpc_get_pageable_list(h, "running", xpath, NULL, nsc, CONTENT_CONFIG, NULL, + "2", "0", + NULL, NULL, NULL, + &xret) < 0){ + goto done; + } + if ((xerr = xpath_first(xret, NULL, "/rpc-error")) != NULL){ + clixon_netconf_error(xerr, "Get configuration", NULL); + goto done; + } + xc = NULL; + while ((xc = xml_child_each(xret, xc, CX_ELMNT)) != NULL) + switch (format){ + case FORMAT_XML: + clicon_xml2file(stdout, xc, 0, 0); + fprintf(stdout, "\n"); + break; + case FORMAT_JSON: + xml2json_cb(stdout, xc, 1, cligen_output); + break; + case FORMAT_TEXT: + xml2txt_cb(stdout, xc, cligen_output); /* tree-formed text */ + break; + case FORMAT_CLI: + xml2cli_cb(stdout, xc, NULL, GT_HIDE, cligen_output); /* cli syntax */ + break; + case FORMAT_NETCONF: + break; + } + retval = 0; + done: + if (xret) + xml_free(xret); + if (cb) + cbuf_free(cb); + return retval; +} + diff --git a/apps/restconf/restconf_methods_get.c b/apps/restconf/restconf_methods_get.c index f1071cec..7bef6e7d 100644 --- a/apps/restconf/restconf_methods_get.c +++ b/apps/restconf/restconf_methods_get.c @@ -663,9 +663,9 @@ api_data_collection(clicon_handle h, sort = cvec_find_str(qvec, "sort"); where = cvec_find_str(qvec, "where"); - if (clicon_rpc_get_collection(h, api_path, y, nsc, content, - depth, count, skip, direction, sort, where, - &xret) < 0){ + if (clicon_rpc_get_pageable_list(h, "running", xpath, y, nsc, content, + depth, count, skip, direction, sort, where, + &xret) < 0){ if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) goto done; if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ diff --git a/example/main/example_cli.cli b/example/main/example_cli.cli index 8f05995d..1f2ac356 100644 --- a/example/main/example_cli.cli +++ b/example/main/example_cli.cli @@ -77,6 +77,7 @@ show("Show a particular state of the system"){ xml("Show comparison in xml"), compare_dbs((int32)0); text("Show comparison in text"), compare_dbs((int32)1); } + pagination("Show list pagination"), cli_pagination("/exm:admins/exm:admin[exm:name='Bob']/exm:skill", "exm", "http://example.com/ns/example-module", "xml"); configuration("Show configuration"), cli_auto_show("datamodel", "candidate", "text", true, false);{ xml("Show configuration as XML"), cli_auto_show("datamodel", "candidate", "xml", true, false); cli("Show configuration as CLI commands"), cli_auto_show("datamodel", "candidate", "cli", true, false, "set "); diff --git a/lib/clixon/clixon_proto_client.h b/lib/clixon/clixon_proto_client.h index 8cfccffc..c27d929b 100644 --- a/lib/clixon/clixon_proto_client.h +++ b/lib/clixon/clixon_proto_client.h @@ -55,7 +55,7 @@ int clicon_rpc_delete_config(clicon_handle h, char *db); int clicon_rpc_lock(clicon_handle h, char *db); int clicon_rpc_unlock(clicon_handle h, char *db); int clicon_rpc_get(clicon_handle h, char *xpath, cvec *nsc, netconf_content content, int32_t depth, cxobj **xret); -int clicon_rpc_get_collection(clicon_handle h, char *apipath, yang_stmt *yco, cvec *nsc, netconf_content content, char *depth, char *count, char *skip, char *direction, char *sort, char *where, cxobj **xt); +int clicon_rpc_get_pageable_list(clicon_handle h, char *datastore, char *xpath, yang_stmt *yco, cvec *nsc, netconf_content content, char *depth, char *count, char *skip, char *direction, char *sort, char *where, cxobj **xt); int clicon_rpc_close_session(clicon_handle h); int clicon_rpc_kill_session(clicon_handle h, uint32_t session_id); int clicon_rpc_validate(clicon_handle h, char *db); diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 9794bd30..dc013128 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -880,8 +880,8 @@ clicon_rpc_get(clicon_handle h, /*! Get database configuration and state data collection * @param[in] h Clicon handle - * @param[in] apipath To identify a list/leaf-list - * @param[in] yli Yang-stmt of list/leaf-list of collection + * @param[in] xpath To identify a list/leaf-list + * @param[in] yli Yang-stmt of list/leaf-list of collection, if given make sanity check * @param[in] namespace Namespace associated w xpath * @param[in] nsc Namespace context for filter * @param[in] content Clixon extension: all, config, noconfig. -1 means all @@ -901,18 +901,19 @@ clicon_rpc_get(clicon_handle h, * @note the netconf return message is yang populated, as well as the return data */ int -clicon_rpc_get_collection(clicon_handle h, - char *apipath, - yang_stmt *yli, - cvec *nsc, /* namespace context for filter */ - netconf_content content, - char *depth, - char *count, - char *skip, - char *direction, - char *sort, - char *where, - cxobj **xt) +clicon_rpc_get_pageable_list(clicon_handle h, + char *datastore, + char *xpath, + yang_stmt *yli, + cvec *nsc, /* namespace context for xpath */ + netconf_content content, + char *depth, + char *count, + char *skip, + char *direction, + char *sort, + char *where, + cxobj **xt) { int retval = -1; struct clicon_msg *msg = NULL; @@ -926,6 +927,9 @@ clicon_rpc_get_collection(clicon_handle h, yang_stmt *yspec; cxobj *x; + if (datastore == NULL){ + clicon_err(OE_XML, EINVAL, "datastore not given"); + } if (session_id_check(h, &session_id) < 0) goto done; if ((cb = cbuf_new()) == NULL) @@ -935,15 +939,20 @@ clicon_rpc_get_collection(clicon_handle h, cprintf(cb, " username=\"%s\"", username); cprintf(cb, " xmlns:%s=\"%s\"", NETCONF_BASE_PREFIX, NETCONF_BASE_NAMESPACE); - cprintf(cb, ">"); - if (count) - cprintf(cb, "%s", apipath); + cprintf(cb, "ds:%s", datastore); + if (xpath){ + cprintf(cb, "%s", xpath); + } if (count) cprintf(cb, "%s", count); if (skip) @@ -954,7 +963,7 @@ clicon_rpc_get_collection(clicon_handle h, cprintf(cb, "%s", sort); if (where) cprintf(cb, "%s", where); - cprintf(cb, ""); + cprintf(cb, ""); if ((msg = clicon_msg_encode(session_id, "%s", cbuf_get(cb))) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) @@ -962,13 +971,13 @@ clicon_rpc_get_collection(clicon_handle h, /* Send xml error back: first check error, then ok */ if ((xr = xpath_first(xret, NULL, "/rpc-reply/rpc-error")) != NULL) xr = xml_parent(xr); /* point to rpc-reply */ - else if ((xr = xpath_first(xret, NULL, "/rpc-reply/collection")) == NULL){ - if ((xr = xml_new("collection", NULL, CX_ELMNT)) == NULL) + else if ((xr = xpath_first(xret, NULL, "/rpc-reply/pageable-list")) == NULL){ + if ((xr = xml_new("pageable_list", NULL, CX_ELMNT)) == NULL) goto done; } - else{ + else if (yli != NULL) { yspec = clicon_dbspec_yang(h); - /* Populate all children with yco */ + /* Populate all children with y */ x = NULL; while ((x = xml_child_each(xr, x, CX_ELMNT)) != NULL){ xml_spec_set(x, yli); @@ -976,8 +985,8 @@ clicon_rpc_get_collection(clicon_handle h, goto done; if (ret == 0){ if (clixon_netconf_internal_error(xerr, - ". Internal error, backend returned invalid XML.", - NULL) < 0) + ". Internal error, backend returned XML tat doid not match given YANG.", + yli?yang_argument_get(yli):NULL) < 0) goto done; if ((xr = xpath_first(xerr, NULL, "rpc-error")) == NULL){ clicon_err(OE_XML, ENOENT, "Expected rpc-error tag but none found(internal)"); diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 36f485eb..b4b251ae 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -1041,7 +1041,7 @@ xml_default_create1(yang_stmt *y, /*! Create leaf from default value * - * @param[in] yt Yang spec + * @param[in] y Yang spec * @param[in] xt XML tree * @param[in] top Use default namespace (if you create xmlns statement) * @retval 0 OK @@ -1056,13 +1056,18 @@ xml_default_create(yang_stmt *y, cxobj *xc = NULL; cxobj *xb; char *str; + cg_var *cv; if (xml_default_create1(y, xt, top, &xc) < 0) goto done; xml_flag_set(xc, XML_FLAG_DEFAULT); if ((xb = xml_new("body", xc, CX_BODY)) == NULL) goto done; - if ((str = cv2str_dup(yang_cv_get(y))) == NULL){ + if ((cv = yang_cv_get(y)) == NULL){ + clicon_err(OE_UNIX, ENOENT, "No yang cv of %s", yang_argument_get(y)); + goto done; + } + if ((str = cv2str_dup(cv)) == NULL){ clicon_err(OE_UNIX, errno, "cv2str_dup"); goto done; } diff --git a/test/test_pagination.sh b/test/test_pagination.sh index 7ddbe6cc..6c3944e7 100755 --- a/test/test_pagination.sh +++ b/test/test_pagination.sh @@ -23,6 +23,9 @@ cat < $cfg $dir/restconf.pidfile $dir true + $APPNAME + /usr/local/lib/$APPNAME/cli + /usr/local/lib/$APPNAME/clispec EOF @@ -286,11 +289,12 @@ if [ $BE -ne 0 ]; then err fi sudo pkill -f clixon_backend # to be sure + new "start backend -s startup -f $cfg -- -sS $mystate" start_backend -s startup -f $cfg -- -sS $fstate fi -new "waiting" +new "wait backend" wait_backend if [ $RC -ne 0 ]; then @@ -300,7 +304,7 @@ if [ $RC -ne 0 ]; then new "start restconf daemon" start_restconf -f $cfg - new "waiting" + new "wait restconf" wait_restconf fi @@ -311,6 +315,11 @@ expecteof "$clixon_netconf -qf $cfg" 0 "ds:running/exm:admins/exm:admin[exm:name='Bob']/exm:skill22]]>]]>" 'Organization44Problem Solving98]]>]]>$' +# CLI +# XXX This relies on a very specific clispec command: need a more generic test +new "cli show" +expectpart "$($clixon_cli -1 -f $cfg -l o show pagination)" 0 "Conflict Resolution93" "Management23" --not-- "Organization44" + # draft-wwlh-netconf-list-pagination-rc-00.txt #new "A.1. 'count' Parameter RESTCONF" #expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang.collection+xml" $RCPROTO://localhost/restconf/data/example-module:get-list-pagination/library/artist=Foo%20Fighters/album/?count=2)" 0 "HTTP/1.1 200 OK" "application/yang.collection+xml" 'Crime and Punishment1995One by One2002' diff --git a/yang/clixon/ietf-netconf-collection@2020-10-22.yang b/yang/clixon/ietf-netconf-collection@2020-10-22.yang deleted file mode 100644 index f9fc125a..00000000 --- a/yang/clixon/ietf-netconf-collection@2020-10-22.yang +++ /dev/null @@ -1,125 +0,0 @@ -module ietf-netconf-collection { - namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-collection"; - prefix "rcoll"; - - organization - "IETF NETCONF (Network Configuration) Working Group"; - - contact - "WG Web: - WG List: - - WG Chair: Mehmet Ersue - - - WG Chair: Mahesh Jethanandani - - - Editor: Andy Bierman - - - Editor: Martin Bjorklund - - - Editor: Kent Watsen - "; - - description - "This module contains conceptual YANG specifications - for the RESTCONF Collection resource type. - Note that the YANG definitions within this module do not - represent configuration data of any kind. - The YANG grouping statements provide a normative syntax - for XML and JSON message encoding purposes. - - Copyright (c) 2015 IETF Trust and the persons identified as - authors of the code. All rights reserved. - - Redistribution and use in source and binary forms, with or - without modification, is permitted pursuant to, and subject - to the license terms contained in, the Simplified BSD License - set forth in Section 4.c of the IETF Trust's Legal Provisions - Relating to IETF Documents - (http://trustee.ietf.org/license-info). - - This version of this YANG module is part of RFC XXXX; see - the RFC itself for full legal notices."; - /* - module: ietf-netconf-collection - rpcs: - - +---x get-collection - +--w input - +--w module-name string - +--w datastore string - +--w list-target string - +--w count uint32 - +--w skip uint32 - +--w direction enum - +--w sort string - +--w where string - +--ro output - +-ro collection anydata - */ - - - revision 2020-10-22 { - description - "Draft by Olof Hagsand / Clixon."; - } - rpc get-collection { - input { - leaf module-name { - /* Not needed with proper list-target */ - type string; - } - leaf datastore { - type string; - default "running"; - } - leaf list-target { - description "api-path"; - mandatory true; - type string; - } - leaf count { - type union { - type uint32; - type string { - pattern 'unbounded'; - } - } - } - leaf skip { - type union { - type uint32; - type string { - pattern 'unbounded'; - } - } - } - leaf direction { - type enumeration { - enum forward; - enum reverse; - } - } - leaf sort { - type string; - } - leaf where { - type string; - } - } - output { - anyxml collection { - description - "Copy of the running datastore subset and/or state - data that matched the filter criteria (if any). - An empty data container indicates that the request did not - produce any results."; - } - } - } - -}