Merge branch 'develop' of https://github.com/clicon/clixon into develop

This commit is contained in:
Olof hagsand 2018-07-16 16:18:36 +02:00
commit 5d7c4a8d18
14 changed files with 203 additions and 46 deletions

View file

@ -2,9 +2,14 @@
## 3.7.0 (Upcoming) ## 3.7.0 (Upcoming)
### Major changes: ### Major changes:
* Full support of XPATH 1.0 according to https://www.w3.org/TR/xpath-10 using yacc/lex * Full support of XPATH 1.0 according to https://www.w3.org/TR/xpath-10 using yacc/lex
* The previous XPATH imlementation was very restricted. * The previous XPATH imlementation was very restricted.
* The only function implemented is the Yang extension "current()". No other functions are implemented (eg last(), count()). * The only function implemented is the Yang extension "current()". No other functions are implemented (eg last(), count()).
* Conformance of restconf(RFC-8040) operations where prefix was used instead of module name.
* Proper specification for an operation is POST /restconf/operations/<module_name>:<rpc_procedure> HTTP/1.1
* See https://github.com/clicon/clixon/issues/31, https://github.com/clicon/clixon/pull/32 and https://github.com/clicon/clixon/issues/30
* Thanks David Cornejo and Dmitry Vakhrushev of Netgate for pointing this out.
* Support for YANG identity and identityref according to RFC 7950 Sec 7.18 and 9.10 * Support for YANG identity and identityref according to RFC 7950 Sec 7.18 and 9.10
* Previous support did no validation of values. * Previous support did no validation of values.
* Validation of types and CLI expansion * Validation of types and CLI expansion
@ -12,7 +17,9 @@
* Applications which have not strictly enforced the identities may now have problems with validation and may need to be modified. * Applications which have not strictly enforced the identities may now have problems with validation and may need to be modified.
### Minor changes: ### Minor changes:
* Dedicated xml,json,yang and xpath parser utility programs added * Added systemd example files under example/systemd
* Changed `plugin_init()` backend return semantics: If returns NULL, _without_ calling clicon_err(), the module is disabled.
* Dedicated standalone xml,json,yang and xpath parser utility test programs added under lib/src/.
* CDATA xml support (patch by David Cornejo, Netgate) * CDATA xml support (patch by David Cornejo, Netgate)
* Encode and decode (parsing) support * Encode and decode (parsing) support
* Validation of yang bits type space-separated list value * Validation of yang bits type space-separated list value

View file

@ -2,7 +2,12 @@
### Installation using Nginx ### Installation using Nginx
Define nginx config file/etc/nginx/sites-available/default Ensure www-data is member of the CLICON_SOCK_GROUP (default clicon). If not, add it:
```
sudo usermod -a -G clicon www-data
```
Define nginx config file: /etc/nginx/sites-available/default
``` ```
server { server {
... ...

View file

@ -925,8 +925,7 @@ api_operations_get(clicon_handle h,
yang_spec *yspec; yang_spec *yspec;
yang_stmt *ym; yang_stmt *ym;
yang_stmt *yc; yang_stmt *yc;
yang_stmt *yprefix; char *modname;
char *prefix;
cbuf *cbx = NULL; cbuf *cbx = NULL;
cxobj *xt = NULL; cxobj *xt = NULL;
@ -937,15 +936,12 @@ api_operations_get(clicon_handle h,
cprintf(cbx, "<operations>"); cprintf(cbx, "<operations>");
ym = NULL; ym = NULL;
while ((ym = yn_each((yang_node*)yspec, ym)) != NULL) { while ((ym = yn_each((yang_node*)yspec, ym)) != NULL) {
if ((yprefix = yang_find((yang_node*)ym, Y_PREFIX, NULL)) != NULL) modname = ym->ys_argument;
prefix = yprefix->ys_argument;
else
continue;
yc = NULL; yc = NULL;
while ((yc = yn_each((yang_node*)ym, yc)) != NULL) { while ((yc = yn_each((yang_node*)ym, yc)) != NULL) {
if (yc->ys_keyword != Y_RPC) if (yc->ys_keyword != Y_RPC)
continue; continue;
cprintf(cbx, "<%s:%s />", prefix, yc->ys_argument); cprintf(cbx, "<%s:%s />", modname, yc->ys_argument);
} }
} }
cprintf(cbx, "</operations>"); cprintf(cbx, "</operations>");
@ -1030,6 +1026,9 @@ api_operations_post(clicon_handle h,
char *username; char *username;
cbuf *cbret = NULL; cbuf *cbret = NULL;
int ret = 0; int ret = 0;
char *prefix = NULL;
char *id = NULL;
yang_stmt *ys = NULL;
clicon_debug(1, "%s json:\"%s\" path:\"%s\"", __FUNCTION__, data, path); clicon_debug(1, "%s json:\"%s\" path:\"%s\"", __FUNCTION__, data, path);
if ((yspec = clicon_dbspec_yang(h)) == NULL){ if ((yspec = clicon_dbspec_yang(h)) == NULL){
@ -1047,8 +1046,22 @@ api_operations_post(clicon_handle h,
} }
clicon_debug(1, "%s oppath: %s", __FUNCTION__, oppath); clicon_debug(1, "%s oppath: %s", __FUNCTION__, oppath);
/* Find yang rpc statement, return yang rpc statement if found */ /* Find yang rpc statement, return yang rpc statement if found
if (yang_abs_schema_nodeid(yspec, oppath, Y_RPC, &yrpc) < 0){ * POST {+restconf}/operations/<operation>
*
* The <operation> field identifies the module name and rpc identifier
* string for the desired operation.
*/
if (yang_nodeid_split(oppath+1, &prefix, &id) < 0) /* +1 skip / */
goto done;
if ((ys = yang_find((yang_node*)yspec, Y_MODULE, prefix)) == NULL){
if (netconf_operation_failed_xml(&xerr, "protocol", "yang module not found") < 0)
goto done;
if (api_return_err(h, r, xerr, pretty, use_xml) < 0)
goto done;
goto ok;
}
if ((yrpc = yang_find((yang_node*)ys, Y_RPC, id)) == NULL){
if (netconf_operation_failed_xml(&xerr, "protocol", "yang node not found") < 0) if (netconf_operation_failed_xml(&xerr, "protocol", "yang node not found") < 0)
goto done; goto done;
if (api_return_err(h, r, xerr, pretty, use_xml) < 0) if (api_return_err(h, r, xerr, pretty, use_xml) < 0)
@ -1229,6 +1242,10 @@ api_operations_post(clicon_handle h,
retval = 0; retval = 0;
done: done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (prefix)
free(prefix);
if (id)
free(id);
if (xdata) if (xdata)
xml_free(xdata); xml_free(xdata);
if (xtop) if (xtop)

View file

@ -1,4 +1,4 @@
i# Clixon FAQ # Clixon FAQ
## What is Clixon? ## What is Clixon?
@ -24,7 +24,7 @@ Clixon is written in C. The plugins are written in C. The CLI
specification uses cligen (http://cligen.se) specification uses cligen (http://cligen.se)
## How to best understand Clixon? ## How to best understand Clixon?
Run the Clixon example, in the example directory. Run the Clixon example, in the [example](../example) directory.
## How do you build and install Clixon (and the example)? ## How do you build and install Clixon (and the example)?
Clixon: Clixon:
@ -41,14 +41,25 @@ The example:
sudo make install sudo make install
``` ```
## Do I need to setup anything? ## Do I need to setup anything? (IMPORTANT)
The config demon requires a valid group to create a server UNIX socket. The config demon requires a valid group to create a server UNIX socket.
Define a valid CLICON_SOCK_GROUP in the config file or via the -g option Define a valid CLICON_SOCK_GROUP in the config file or via the -g option
or create the group and add the user to it. The default group is 'clicon'. or create the group and add the user to it. The default group is 'clicon'.
Add yourself and www-data, if you intend to use restconf.
On linux: On linux:
```
sudo groupadd clicon sudo groupadd clicon
sudo usermod -a -G clicon user sudo usermod -a -G clicon <user>
sudo usermod -a -G clicon www-data
```
Verify:
```
grep clicon /etc/group
clicon:x:1001:<user>,www-data
```
## What about reference documentation? ## What about reference documentation?
Clixon uses Doxygen for reference documentation. Clixon uses Doxygen for reference documentation.
@ -178,6 +189,11 @@ You may also add a default method in the configuration file:
</config> </config>
``` ```
## Can I use systemd with Clixon?
Yes. Systemd example files are provide for the backend and the
restconf daemon as part of the [example](../example/systemd).
## How can I add extra XML? ## How can I add extra XML?
There are two ways to add extra XML to running database after start. Note that this XML is not "committed" into running. There are two ways to add extra XML to running database after start. Note that this XML is not "committed" into running.
@ -345,7 +361,7 @@ To authenticate, the callback needs to return the value 1 and supply a username.
See [../apps/example/example_restconf.c] example_restconf_credentials() for See [../apps/example/example_restconf.c] example_restconf_credentials() for
an example of HTTP basic auth. an example of HTTP basic auth.
## How do I write a CLI translator function ## How do I write a CLI translator function?
The CLI can perform variable translation. This is useful if you want to The CLI can perform variable translation. This is useful if you want to
prcess the input, such as hashing, encrypting or in other way prcess the input, such as hashing, encrypting or in other way

View file

@ -16,14 +16,18 @@ routing example. It contains the following files:
* example_netconf.c Netconf callback plugin * example_netconf.c Netconf callback plugin
* Makefile.in Example makefile where plugins are built and installed * Makefile.in Example makefile where plugins are built and installed
## Compile and run ## Compile and run
Before you start, see [preparation](../doc/FAQ.md#do-i-need-to-setup-anything-important).
``` ```
cd example cd example
make && sudo make install make && sudo make install
``` ```
Start backend: Start backend:
``` ```
clixon_backend -f /usr/local/etc/example.xml -I sudo clixon_backend -f /usr/local/etc/example.xml -s init
``` ```
Edit cli: Edit cli:
``` ```
@ -188,6 +192,10 @@ The example contains some stubs for authorization according to [RFC8341(NACM)](h
* A NACM backend plugin reporting the mandatory NACM state variables. * A NACM backend plugin reporting the mandatory NACM state variables.
## Systemd files
Example systemd files for backend and restconf daemons are found under the systemd directory. Install them under /etc/systemd/system for example.
## Run as docker container ## Run as docker container
(Note not updated) (Note not updated)

View file

@ -114,6 +114,13 @@ static clixon_plugin_api api = {
clixon_plugin_api * clixon_plugin_api *
clixon_plugin_init(clicon_handle h) clixon_plugin_init(clicon_handle h)
{ {
char *nacm_mode;
clicon_debug(1, "%s backend nacm", __FUNCTION__); clicon_debug(1, "%s backend nacm", __FUNCTION__);
nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE");
if (nacm_mode==NULL || strcmp(nacm_mode, "disabled") == 0){
clicon_debug(1, "%s CLICON_NACM_MODE not enabled: example nacm module disabled", __FUNCTION__);
return NULL;
}
return &api; return &api;
} }

View file

@ -0,0 +1,13 @@
[Unit]
Description=Starts and stops a clixon example service on this system
Wants=example_restconf.service
[Service]
Type=forking
User=root
RestartSec=60
Restart=on-failure
ExecStart=/usr/local/sbin/clixon_backend -s running -f /usr/local/etc/example.xml
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,14 @@
[Unit]
Description=Starts and stops an example clixon restconf service on this system
Wants=example.service
After=example.service
[Service]
Type=simple
User=www-data
WorkingDirectory=/www-data
Restart=on-failure
ExecStart=/www-data/clixon_restconf -f /usr/local/etc/example.xml
[Install]
WantedBy=multi-user.target

View file

@ -41,7 +41,9 @@
/* /*
* Constants * Constants
*/ */
/* Hardcoded plugin symbol. Must exist in all plugins to kickstart */ /* Hardcoded plugin symbol. Must exist in all plugins to kickstart
* @see clixon_plugin_init
*/
#define CLIXON_PLUGIN_INIT "clixon_plugin_init" #define CLIXON_PLUGIN_INIT "clixon_plugin_init"
/* /*
@ -181,6 +183,7 @@ typedef struct clixon_plugin clixon_plugin;
/*! Plugin initialization function. Must appear in all plugins /*! Plugin initialization function. Must appear in all plugins
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @retval api Pointer to API struct * @retval api Pointer to API struct
* @retval NULL Failure (if clixon_err() called), module disabled otherwise.
* @see CLIXON_PLUGIN_INIT default symbol * @see CLIXON_PLUGIN_INIT default symbol
*/ */
clixon_plugin_api *clixon_plugin_init(clicon_handle h); clixon_plugin_api *clixon_plugin_init(clicon_handle h);

View file

@ -246,6 +246,7 @@ yang_stmt *yn_each(yang_node *yn, yang_stmt *ys);
char *yang_key2str(int keyword); char *yang_key2str(int keyword);
char *yarg_prefix(yang_stmt *ys); char *yarg_prefix(yang_stmt *ys);
char *yarg_id(yang_stmt *ys); char *yarg_id(yang_stmt *ys);
int yang_nodeid_split(char *nodeid, char **prefix, char **id);
yang_stmt *ys_module(yang_stmt *ys); yang_stmt *ys_module(yang_stmt *ys);
yang_spec *ys_spec(yang_stmt *ys); yang_spec *ys_spec(yang_stmt *ys);
yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix); yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix);

View file

@ -43,6 +43,7 @@
#include <errno.h> #include <errno.h>
#include <dlfcn.h> #include <dlfcn.h>
#include <dirent.h> #include <dirent.h>
#include <syslog.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/param.h> #include <sys/param.h>
@ -204,13 +205,17 @@ plugin_load_one(clicon_handle h,
clicon_err(OE_UNIX, 0, "dlsym: %s: %s", file, error); clicon_err(OE_UNIX, 0, "dlsym: %s: %s", file, error);
goto done; goto done;
} }
clicon_err_reset();
if ((api = initfn(h)) == NULL) { if ((api = initfn(h)) == NULL) {
if (!clicon_errno){ /* if clicon_err() is not called then log and continue */
clicon_log(LOG_WARNING, "Warning: failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file);
dlclose(handle);
}
else{
clicon_err(OE_PLUGIN, errno, "Failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file); clicon_err(OE_PLUGIN, errno, "Failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file);
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
clicon_err(OE_DB, 0, "Unknown error: %s: plugin_init does not make clicon_err call on error",
file);
goto err; goto err;
} }
}
/* Note: sizeof clixon_plugin_api which is largest of clixon_plugin_api:s */ /* Note: sizeof clixon_plugin_api which is largest of clixon_plugin_api:s */
if ((cp = (clixon_plugin *)malloc(sizeof(struct clixon_plugin))) == NULL){ if ((cp = (clixon_plugin *)malloc(sizeof(struct clixon_plugin))) == NULL){
clicon_err(OE_UNIX, errno, "malloc"); clicon_err(OE_UNIX, errno, "malloc");
@ -228,6 +233,7 @@ plugin_load_one(clicon_handle h,
snprintf(cp->cp_name, sizeof(cp->cp_name), "%*s", snprintf(cp->cp_name, sizeof(cp->cp_name), "%*s",
(int)strlen(name), name); (int)strlen(name), name);
if (api)
cp->cp_api = *api; cp->cp_api = *api;
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
done: done:

View file

@ -1725,6 +1725,7 @@ xml_merge1(cxobj *x0,
cxobj *x1c; /* mod child */ cxobj *x1c; /* mod child */
char *x1bstr; /* mod body string */ char *x1bstr; /* mod body string */
yang_stmt *yc; /* yang child */ yang_stmt *yc; /* yang child */
cbuf *cbr = NULL; /* Reason buffer */
assert(x1 && xml_type(x1) == CX_ELMNT); assert(x1 && xml_type(x1) == CX_ELMNT);
assert(y0); assert(y0);
@ -1763,10 +1764,17 @@ xml_merge1(cxobj *x0,
x1cname = xml_name(x1c); x1cname = xml_name(x1c);
/* Get yang spec of the child */ /* Get yang spec of the child */
if ((yc = yang_find_datanode(y0, x1cname)) == NULL){ if ((yc = yang_find_datanode(y0, x1cname)) == NULL){
if (reason && (*reason = strdup("XML node has no corresponding yang specification (Invalid XML or wrong Yang spec?")) == NULL){ if (reason){
if ((cbr = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
cprintf(cbr, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?", xml_name(x1), x1cname);
if ((*reason = strdup(cbuf_get(cbr))) == NULL){
clicon_err(OE_UNIX, errno, "strdup"); clicon_err(OE_UNIX, errno, "strdup");
goto done; goto done;
} }
}
break; break;
} }
/* See if there is a corresponding node in the base tree */ /* See if there is a corresponding node in the base tree */
@ -1782,6 +1790,8 @@ xml_merge1(cxobj *x0,
ok: ok:
retval = 0; retval = 0;
done: done:
if (cbr)
cbuf_free(cbr);
return retval; return retval;
} }
@ -1807,6 +1817,7 @@ xml_merge(cxobj *x0,
cxobj *x0c; /* base child */ cxobj *x0c; /* base child */
cxobj *x1c; /* mod child */ cxobj *x1c; /* mod child */
yang_stmt *yc; yang_stmt *yc;
cbuf *cbr = NULL; /* Reason buffer */
/* Loop through children of the modification tree */ /* Loop through children of the modification tree */
x1c = NULL; x1c = NULL;
@ -1814,10 +1825,17 @@ xml_merge(cxobj *x0,
x1cname = xml_name(x1c); x1cname = xml_name(x1c);
/* Get yang spec of the child */ /* Get yang spec of the child */
if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == NULL){ if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == NULL){
if (reason && (*reason = strdup("XML node has no corresponding yang specification (Invalid XML or wrong Yang spec?")) == NULL){ if (reason){
if ((cbr = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
cprintf(cbr, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?", xml_name(x1), x1cname);
if ((*reason = strdup(cbuf_get(cbr))) == NULL){
clicon_err(OE_UNIX, errno, "strdup"); clicon_err(OE_UNIX, errno, "strdup");
goto done; goto done;
} }
}
break; break;
} }
/* See if there is a corresponding node in the base tree */ /* See if there is a corresponding node in the base tree */
@ -1830,6 +1848,8 @@ xml_merge(cxobj *x0,
} }
retval = 0; /* OK */ retval = 0; /* OK */
done: done:
if (cbr)
cbuf_free(cbr);
return retval; return retval;
} }

View file

@ -611,8 +611,8 @@ yang_find_schemanode(yang_node *yn,
/*! Find first matching data node in all (sub)modules in a yang spec /*! Find first matching data node in all (sub)modules in a yang spec
* *
* @param[in] ysp Yang specification * @param[in] ysp Yang specification
* @param[in] argument if NULL, match any(first) argument. XXX is that really a case? * @param[in] argument Name of node. If NULL match first
* @param[in] schemanode If set look for schema nodes, otherwise only data nodes * @param[in] class See yang_class for class of yang nodes
* A yang specification has modules as children which in turn can have * A yang specification has modules as children which in turn can have
* syntax-nodes as children. This function goes through all the modules to * syntax-nodes as children. This function goes through all the modules to
* look for 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,
@ -803,7 +803,7 @@ yarg_id(yang_stmt *ys)
return id; return id;
} }
/* Assume argument is id on the type: <[prefix:]id>, return 'prefix' /*! Assume argument is id on the type: <[prefix:]id>, return 'prefix'
* @param[in] ys A yang statement * @param[in] ys A yang statement
* @retval NULL No prefix * @retval NULL No prefix
* @retval prefix Malloced string that needs to be freed by caller. * @retval prefix Malloced string that needs to be freed by caller.
@ -822,6 +822,46 @@ yarg_prefix(yang_stmt *ys)
return prefix; return prefix;
} }
/*! Split yang node identifier into prefix and identifer.
* @param[in] node-id
* @param[out] prefix Malloced string. May be NULL.
* @param[out] id Malloced identifier.
* @retval 0 OK
* @retval -1 Error
* @note caller need to free id and prefix after use
*/
int
yang_nodeid_split(char *nodeid,
char **prefix,
char **id)
{
int retval = -1;
char *str;
if ((str = strchr(nodeid, ':')) == NULL){
if ((*id = strdup(nodeid)) == NULL){
clicon_err(OE_YANG, errno, "strdup");
goto done;
}
}
else{
if ((*prefix = strdup(nodeid)) == NULL){
clicon_err(OE_YANG, errno, "strdup");
goto done;
}
(*prefix)[str-nodeid] = '\0';
str++;
if ((*id = strdup(str)) == NULL){
clicon_err(OE_YANG, errno, "strdup");
goto done;
}
}
retval = 0;
done:
return retval;
}
/*! Given a yang statement and a prefix, return yang module to that prefix /*! Given a yang statement and a prefix, return yang module to that prefix
* Note, not the other module but the proxy import statement only * Note, not the other module but the proxy import statement only
* @param[in] ys A yang statement * @param[in] ys A yang statement

View file

@ -92,7 +92,7 @@ new "kill old restconf daemon"
sudo pkill -u www-data clixon_restconf sudo pkill -u www-data clixon_restconf
new "start restconf daemon" new "start restconf daemon"
sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang # -D sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang -D
sleep 1 sleep 1
@ -113,12 +113,12 @@ expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/r
' '
new2 "restconf get restconf/operations. RFC8040 3.3.2 (json)" new2 "restconf get restconf/operations. RFC8040 3.3.2 (json)"
expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"ex:client-rpc": null,"rt:fib-route": null,"rt:route-count": null}} expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"example:empty": null,"example:input": null,"example:output": null,"example:client-rpc": null,"ietf-routing:fib-route": null,"ietf-routing:route-count": null}}
' '
new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" new "restconf get restconf/operations. RFC8040 3.3.2 (xml)"
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations)
expect="<operations><ex:empty/><ex:input/><ex:output/><ex:client-rpc/><rt:fib-route/><rt:route-count/></operations>" expect="<operations><example:empty/><example:input/><example:output/><example:client-rpc/><ietf-routing:fib-route/><ietf-routing:route-count/></operations>"
match=`echo $ret | grep -EZo "$expect"` match=`echo $ret | grep -EZo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
err "$expect" "$ret" err "$expect" "$ret"
@ -143,7 +143,7 @@ expectfn "curl -s -I http://localhost/restconf/data" 0 "HTTP/1.1 200 OK"
#Content-Type: application/yang-data+json" #Content-Type: application/yang-data+json"
new2 "restconf empty rpc" new2 "restconf empty rpc"
expecteq "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/ex:empty)" "" expecteq "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/example:empty)" ""
new2 "restconf get empty config + state json" new2 "restconf get empty config + state json"
expecteq "$(curl -sSG http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}} expecteq "$(curl -sSG http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}}
@ -246,29 +246,29 @@ expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces":
' '
new2 "restconf rpc using POST json" new2 "restconf rpc using POST json"
expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route)" '{"output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"}}}} expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"}}}}
' '
# Cant get this to work due to quoting # Cant get this to work due to quoting
#new2 "restconf rpc using POST wrong JSON" #new2 "restconf rpc using POST wrong JSON"
#expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":ipv4}}' http://localhost/restconf/operations/rt:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": " on line 1: syntax error at or before: i"}}}} ' #expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":ipv4}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": " on line 1: syntax error at or before: i"}}}} '
new2 "restconf rpc using POST json w/o mandatory element" new2 "restconf rpc using POST json w/o mandatory element"
expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}} ' expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}} '
new2 "restconf rpc non-existing rpc w/o namespace" new2 "restconf rpc non-existing rpc w/o namespace"
expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}} ' expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}} '
new2 "restconf rpc non-existing rpc" new2 "restconf rpc non-existing rpc"
expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/ex:kalle)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}} ' expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/example:kalle)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}} '
new2 "restconf rpc missing name" new2 "restconf rpc missing name"
expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Operation name expected"}}}} ' expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Operation name expected"}}}} '
new2 "restconf rpc missing input" new2 "restconf rpc missing input"
expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/rt:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}} ' expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}} '
new "restconf rpc using POST xml" new "restconf rpc using POST xml"
ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route) ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)
expect="<output><route><address-family>ipv4</address-family><next-hop><next-hop-list>2.3.4.5</next-hop-list></next-hop></route></output>" expect="<output><route><address-family>ipv4</address-family><next-hop><next-hop-list>2.3.4.5</next-hop-list></next-hop></route></output>"
match=`echo $ret | grep -EZo "$expect"` match=`echo $ret | grep -EZo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
@ -276,10 +276,10 @@ if [ -z "$match" ]; then
fi fi
new2 "restconf rpc using wrong prefix" new2 "restconf rpc using wrong prefix"
expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}} ' expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang module not found"}}}} '
new "restconf local client rpc using POST xml" new "restconf local client rpc using POST xml"
ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"request":"example"}}' http://localhost/restconf/operations/ex:client-rpc) ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"request":"example"}}' http://localhost/restconf/operations/example:client-rpc)
expect="<output><result>ok</result></output>" expect="<output><result>ok</result></output>"
match=`echo $ret | grep -EZo "$expect"` match=`echo $ret | grep -EZo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
@ -287,7 +287,7 @@ if [ -z "$match" ]; then
fi fi
# XXX cant get -H to work # XXX cant get -H to work
#expecteq 'curl -s -X POST -H "Accept: application/yang-data+xml" -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/rt:fib-route' '<output><route><address-family>ipv4</address-family><next-hop><next-hop-list>2.3.4.5</next-hop-list></next-hop></route></output>' #expecteq 'curl -s -X POST -H "Accept: application/yang-data+xml" -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/ietf-routing:fib-route' '<output><route><address-family>ipv4</address-family><next-hop><next-hop-list>2.3.4.5</next-hop-list></next-hop></route></output>'
# Cant get shell macros to work, inline matching from lib.sh # Cant get shell macros to work, inline matching from lib.sh