cli pagination

This commit is contained in:
Olof hagsand 2020-11-24 13:14:21 +01:00
parent 8e266dd136
commit 2b5dceb82c
9 changed files with 130 additions and 157 deletions

View file

@ -1530,6 +1530,7 @@ from_client_get_pageable_list(clicon_handle h,
clicon_err(OE_UNIX, errno, "cbuf_new"); clicon_err(OE_UNIX, errno, "cbuf_new");
goto done; goto done;
} }
/* This uses xpath. Maybe count/limit should use parameters */
cprintf(cb, "%s", xpath); cprintf(cb, "%s", xpath);
if (where) if (where)
cprintf(cb, "[%s]", where); cprintf(cb, "[%s]", where);

View file

@ -1312,3 +1312,76 @@ cli_help(clicon_handle h, cvec *vars, cvec *argv)
return cligen_help(ch, stdout, pt); 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: <xpath> <prefix> <namespace>
*/
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: <xpath> <prefix> <namespace> <format>");
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;
}

View file

@ -663,7 +663,7 @@ api_data_collection(clicon_handle h,
sort = cvec_find_str(qvec, "sort"); sort = cvec_find_str(qvec, "sort");
where = cvec_find_str(qvec, "where"); where = cvec_find_str(qvec, "where");
if (clicon_rpc_get_collection(h, api_path, y, nsc, content, if (clicon_rpc_get_pageable_list(h, "running", xpath, y, nsc, content,
depth, count, skip, direction, sort, where, depth, count, skip, direction, sort, where,
&xret) < 0){ &xret) < 0){
if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0)

View file

@ -77,6 +77,7 @@ show("Show a particular state of the system"){
xml("Show comparison in xml"), compare_dbs((int32)0); xml("Show comparison in xml"), compare_dbs((int32)0);
text("Show comparison in text"), compare_dbs((int32)1); 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);{ configuration("Show configuration"), cli_auto_show("datamodel", "candidate", "text", true, false);{
xml("Show configuration as XML"), cli_auto_show("datamodel", "candidate", "xml", 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 "); cli("Show configuration as CLI commands"), cli_auto_show("datamodel", "candidate", "cli", true, false, "set ");

View file

@ -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_lock(clicon_handle h, char *db);
int clicon_rpc_unlock(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(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_close_session(clicon_handle h);
int clicon_rpc_kill_session(clicon_handle h, uint32_t session_id); int clicon_rpc_kill_session(clicon_handle h, uint32_t session_id);
int clicon_rpc_validate(clicon_handle h, char *db); int clicon_rpc_validate(clicon_handle h, char *db);

View file

@ -880,8 +880,8 @@ clicon_rpc_get(clicon_handle h,
/*! Get database configuration and state data collection /*! Get database configuration and state data collection
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] apipath To identify a list/leaf-list * @param[in] xpath To identify a list/leaf-list
* @param[in] yli Yang-stmt of list/leaf-list of collection * @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] namespace Namespace associated w xpath
* @param[in] nsc Namespace context for filter * @param[in] nsc Namespace context for filter
* @param[in] content Clixon extension: all, config, noconfig. -1 means all * @param[in] content Clixon extension: all, config, noconfig. -1 means all
@ -901,10 +901,11 @@ clicon_rpc_get(clicon_handle h,
* @note the netconf return message is yang populated, as well as the return data * @note the netconf return message is yang populated, as well as the return data
*/ */
int int
clicon_rpc_get_collection(clicon_handle h, clicon_rpc_get_pageable_list(clicon_handle h,
char *apipath, char *datastore,
char *xpath,
yang_stmt *yli, yang_stmt *yli,
cvec *nsc, /* namespace context for filter */ cvec *nsc, /* namespace context for xpath */
netconf_content content, netconf_content content,
char *depth, char *depth,
char *count, char *count,
@ -926,6 +927,9 @@ clicon_rpc_get_collection(clicon_handle h,
yang_stmt *yspec; yang_stmt *yspec;
cxobj *x; cxobj *x;
if (datastore == NULL){
clicon_err(OE_XML, EINVAL, "datastore not given");
}
if (session_id_check(h, &session_id) < 0) if (session_id_check(h, &session_id) < 0)
goto done; goto done;
if ((cb = cbuf_new()) == NULL) if ((cb = cbuf_new()) == NULL)
@ -935,15 +939,20 @@ clicon_rpc_get_collection(clicon_handle h,
cprintf(cb, " username=\"%s\"", username); cprintf(cb, " username=\"%s\"", username);
cprintf(cb, " xmlns:%s=\"%s\"", cprintf(cb, " xmlns:%s=\"%s\"",
NETCONF_BASE_PREFIX, NETCONF_BASE_NAMESPACE); NETCONF_BASE_PREFIX, NETCONF_BASE_NAMESPACE);
cprintf(cb, "><get-collection xmlns=\"%s\"", NETCONF_COLLECTION_NAMESPACE); cprintf(cb, "><get-pageable-list xmlns=\"%s\"", NETCONF_COLLECTION_NAMESPACE);
/* Clixon extension, content=all,config, or nonconfig */ /* Clixon extension, content=all,config, or nonconfig */
if ((int)content != -1) if ((int)content != -1)
cprintf(cb, " content=\"%s\"", netconf_content_int2str(content)); cprintf(cb, " content=\"%s\"", netconf_content_int2str(content));
if (depth) if (depth)
cprintf(cb, " depth=\"%s\"", depth); cprintf(cb, " depth=\"%s\"", depth);
cprintf(cb, ">"); cprintf(cb, ">");
if (count) cprintf(cb, "<datastore xmlns:ds=\"urn:ietf:params:xml:ns:yang:ietf-datastores\">ds:%s</datastore>", datastore);
cprintf(cb, "<list-target>%s</list-target>", apipath); if (xpath){
cprintf(cb, "<list-target");
if (xml_nsctx_cbuf(cb, nsc) < 0)
goto done;
cprintf(cb, ">%s</list-target>", xpath);
}
if (count) if (count)
cprintf(cb, "<count>%s</count>", count); cprintf(cb, "<count>%s</count>", count);
if (skip) if (skip)
@ -954,7 +963,7 @@ clicon_rpc_get_collection(clicon_handle h,
cprintf(cb, "<sort>%s</sort>", sort); cprintf(cb, "<sort>%s</sort>", sort);
if (where) if (where)
cprintf(cb, "<where>%s</where>", where); cprintf(cb, "<where>%s</where>", where);
cprintf(cb, "</get-collection></rpc>"); cprintf(cb, "</get-pageable-list></rpc>");
if ((msg = clicon_msg_encode(session_id, "%s", cbuf_get(cb))) == NULL) if ((msg = clicon_msg_encode(session_id, "%s", cbuf_get(cb))) == NULL)
goto done; goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) 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 */ /* Send xml error back: first check error, then ok */
if ((xr = xpath_first(xret, NULL, "/rpc-reply/rpc-error")) != NULL) if ((xr = xpath_first(xret, NULL, "/rpc-reply/rpc-error")) != NULL)
xr = xml_parent(xr); /* point to rpc-reply */ xr = xml_parent(xr); /* point to rpc-reply */
else if ((xr = xpath_first(xret, NULL, "/rpc-reply/collection")) == NULL){ else if ((xr = xpath_first(xret, NULL, "/rpc-reply/pageable-list")) == NULL){
if ((xr = xml_new("collection", NULL, CX_ELMNT)) == NULL) if ((xr = xml_new("pageable_list", NULL, CX_ELMNT)) == NULL)
goto done; goto done;
} }
else{ else if (yli != NULL) {
yspec = clicon_dbspec_yang(h); yspec = clicon_dbspec_yang(h);
/* Populate all children with yco */ /* Populate all children with y */
x = NULL; x = NULL;
while ((x = xml_child_each(xr, x, CX_ELMNT)) != NULL){ while ((x = xml_child_each(xr, x, CX_ELMNT)) != NULL){
xml_spec_set(x, yli); xml_spec_set(x, yli);
@ -976,8 +985,8 @@ clicon_rpc_get_collection(clicon_handle h,
goto done; goto done;
if (ret == 0){ if (ret == 0){
if (clixon_netconf_internal_error(xerr, if (clixon_netconf_internal_error(xerr,
". Internal error, backend returned invalid XML.", ". Internal error, backend returned XML tat doid not match given YANG.",
NULL) < 0) yli?yang_argument_get(yli):NULL) < 0)
goto done; goto done;
if ((xr = xpath_first(xerr, NULL, "rpc-error")) == NULL){ if ((xr = xpath_first(xerr, NULL, "rpc-error")) == NULL){
clicon_err(OE_XML, ENOENT, "Expected rpc-error tag but none found(internal)"); clicon_err(OE_XML, ENOENT, "Expected rpc-error tag but none found(internal)");

View file

@ -1041,7 +1041,7 @@ xml_default_create1(yang_stmt *y,
/*! Create leaf from default value /*! Create leaf from default value
* *
* @param[in] yt Yang spec * @param[in] y Yang spec
* @param[in] xt XML tree * @param[in] xt XML tree
* @param[in] top Use default namespace (if you create xmlns statement) * @param[in] top Use default namespace (if you create xmlns statement)
* @retval 0 OK * @retval 0 OK
@ -1056,13 +1056,18 @@ xml_default_create(yang_stmt *y,
cxobj *xc = NULL; cxobj *xc = NULL;
cxobj *xb; cxobj *xb;
char *str; char *str;
cg_var *cv;
if (xml_default_create1(y, xt, top, &xc) < 0) if (xml_default_create1(y, xt, top, &xc) < 0)
goto done; goto done;
xml_flag_set(xc, XML_FLAG_DEFAULT); xml_flag_set(xc, XML_FLAG_DEFAULT);
if ((xb = xml_new("body", xc, CX_BODY)) == NULL) if ((xb = xml_new("body", xc, CX_BODY)) == NULL)
goto done; 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"); clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done; goto done;
} }

View file

@ -23,6 +23,9 @@ cat <<EOF > $cfg
<CLICON_BACKEND_PIDFILE>$dir/restconf.pidfile</CLICON_BACKEND_PIDFILE> <CLICON_BACKEND_PIDFILE>$dir/restconf.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR> <CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_STREAM_DISCOVERY_RFC8040>true</CLICON_STREAM_DISCOVERY_RFC8040> <CLICON_STREAM_DISCOVERY_RFC8040>true</CLICON_STREAM_DISCOVERY_RFC8040>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
</clixon-config> </clixon-config>
EOF EOF
@ -286,11 +289,12 @@ if [ $BE -ne 0 ]; then
err err
fi fi
sudo pkill -f clixon_backend # to be sure sudo pkill -f clixon_backend # to be sure
new "start backend -s startup -f $cfg -- -sS $mystate" new "start backend -s startup -f $cfg -- -sS $mystate"
start_backend -s startup -f $cfg -- -sS $fstate start_backend -s startup -f $cfg -- -sS $fstate
fi fi
new "waiting" new "wait backend"
wait_backend wait_backend
if [ $RC -ne 0 ]; then if [ $RC -ne 0 ]; then
@ -300,7 +304,7 @@ if [ $RC -ne 0 ]; then
new "start restconf daemon" new "start restconf daemon"
start_restconf -f $cfg start_restconf -f $cfg
new "waiting" new "wait restconf"
wait_restconf wait_restconf
fi fi
@ -311,6 +315,11 @@ expecteof "$clixon_netconf -qf $cfg" 0 "<rpc message-id=\"101\" xmlns=\"urn:ietf
new "C.2. 'skip' Parameter NETCONF" new "C.2. 'skip' Parameter NETCONF"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc message-id=\"101\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><get-pageable-list xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\"><datastore xmlns:ds=\"urn:ietf:params:xml:ns:yang:ietf-datastores\">ds:running</datastore><list-target xmlns:exm=\"http://example.com/ns/example-module\">/exm:admins/exm:admin[exm:name='Bob']/exm:skill</list-target><count>2</count><skip>2</skip></get-pageable-list></rpc>]]>]]>" '<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101"><pageable-list xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination"><skill xmlns="http://example.com/ns/example-module"><name>Organization</name><rank>44</rank></skill><skill xmlns="http://example.com/ns/example-module"><name>Problem Solving</name><rank>98</rank></skill></pageable-list></rpc-reply>]]>]]>$' expecteof "$clixon_netconf -qf $cfg" 0 "<rpc message-id=\"101\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><get-pageable-list xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\"><datastore xmlns:ds=\"urn:ietf:params:xml:ns:yang:ietf-datastores\">ds:running</datastore><list-target xmlns:exm=\"http://example.com/ns/example-module\">/exm:admins/exm:admin[exm:name='Bob']/exm:skill</list-target><count>2</count><skip>2</skip></get-pageable-list></rpc>]]>]]>" '<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101"><pageable-list xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination"><skill xmlns="http://example.com/ns/example-module"><name>Organization</name><rank>44</rank></skill><skill xmlns="http://example.com/ns/example-module"><name>Problem Solving</name><rank>98</rank></skill></pageable-list></rpc-reply>]]>]]>$'
# 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 "<skill xmlns=\"http://example.com/ns/example-module\"><name>Conflict Resolution</name><rank>93</rank></skill>" "<skill xmlns=\"http://example.com/ns/example-module\"><name>Management</name><rank>23</rank></skill>" --not-- "<skill xmlns=\"http://example.com/ns/example-module\"><name>Organization</name><rank>44</rank></skill>"
# draft-wwlh-netconf-list-pagination-rc-00.txt # draft-wwlh-netconf-list-pagination-rc-00.txt
#new "A.1. 'count' Parameter RESTCONF" #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" '<collection xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-collection"><album xmlns="http://example.com/ns/example-jukebox"><name>Crime and Punishment</name><year>1995</year></album><album xmlns="http://example.com/ns/example-jukebox"><name>One by One</name><year>2002</year></album></collection>' #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" '<collection xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-collection"><album xmlns="http://example.com/ns/example-jukebox"><name>Crime and Punishment</name><year>1995</year></album><album xmlns="http://example.com/ns/example-jukebox"><name>One by One</name><year>2002</year></album></collection>'

View file

@ -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: <http://tools.ietf.org/wg/netconf/>
WG List: <mailto:netconf@ietf.org>
WG Chair: Mehmet Ersue
<mailto:mehmet.ersue@nsn.com>
WG Chair: Mahesh Jethanandani
<mailto:mjethanandani@gmail.com>
Editor: Andy Bierman
<mailto:andy@yumaworks.com>
Editor: Martin Bjorklund
<mailto:mbj@tail-f.com>
Editor: Kent Watsen
<mailto:kwatsen@juniper.net>";
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.";
}
}
}
}