cli pagination
This commit is contained in:
parent
8e266dd136
commit
2b5dceb82c
9 changed files with 130 additions and 157 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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: <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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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){
|
||||
|
|
|
|||
|
|
@ -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 ");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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, "><get-collection xmlns=\"%s\"", NETCONF_COLLECTION_NAMESPACE);
|
||||
cprintf(cb, "><get-pageable-list xmlns=\"%s\"", NETCONF_COLLECTION_NAMESPACE);
|
||||
/* Clixon extension, content=all,config, or nonconfig */
|
||||
if ((int)content != -1)
|
||||
cprintf(cb, " content=\"%s\"", netconf_content_int2str(content));
|
||||
if (depth)
|
||||
cprintf(cb, " depth=\"%s\"", depth);
|
||||
cprintf(cb, ">");
|
||||
if (count)
|
||||
cprintf(cb, "<list-target>%s</list-target>", apipath);
|
||||
cprintf(cb, "<datastore xmlns:ds=\"urn:ietf:params:xml:ns:yang:ietf-datastores\">ds:%s</datastore>", datastore);
|
||||
if (xpath){
|
||||
cprintf(cb, "<list-target");
|
||||
if (xml_nsctx_cbuf(cb, nsc) < 0)
|
||||
goto done;
|
||||
cprintf(cb, ">%s</list-target>", xpath);
|
||||
}
|
||||
if (count)
|
||||
cprintf(cb, "<count>%s</count>", count);
|
||||
if (skip)
|
||||
|
|
@ -954,7 +963,7 @@ clicon_rpc_get_collection(clicon_handle h,
|
|||
cprintf(cb, "<sort>%s</sort>", sort);
|
||||
if (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)
|
||||
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)");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ cat <<EOF > $cfg
|
|||
<CLICON_BACKEND_PIDFILE>$dir/restconf.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
|
||||
<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>
|
||||
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 "<rpc message-id=\"101\" xmlns=\"urn:ietf
|
|||
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>]]>]]>$'
|
||||
|
||||
# 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
|
||||
#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>'
|
||||
|
|
|
|||
|
|
@ -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.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue