Merge branch 'develop' of https://github.com/clicon/clixon into develop
This commit is contained in:
commit
5d7c4a8d18
14 changed files with 203 additions and 46 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
...
|
...
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
26
doc/FAQ.md
26
doc/FAQ.md
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
example/systemd/example.service
Normal file
13
example/systemd/example.service
Normal 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
|
||||||
14
example/systemd/example_restconf.service
Normal file
14
example/systemd/example_restconf.service
Normal 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
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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,12 +205,16 @@ 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) {
|
||||||
clicon_err(OE_PLUGIN, errno, "Failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file);
|
if (!clicon_errno){ /* if clicon_err() is not called then log and continue */
|
||||||
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
|
clicon_log(LOG_WARNING, "Warning: failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file);
|
||||||
clicon_err(OE_DB, 0, "Unknown error: %s: plugin_init does not make clicon_err call on error",
|
dlclose(handle);
|
||||||
file);
|
}
|
||||||
goto err;
|
else{
|
||||||
|
clicon_err(OE_PLUGIN, errno, "Failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file);
|
||||||
|
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){
|
||||||
|
|
@ -228,7 +233,8 @@ 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);
|
||||||
cp->cp_api = *api;
|
if (api)
|
||||||
|
cp->cp_api = *api;
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
done:
|
done:
|
||||||
return cp;
|
return cp;
|
||||||
|
|
|
||||||
|
|
@ -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,9 +1764,16 @@ 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){
|
||||||
clicon_err(OE_UNIX, errno, "strdup");
|
if ((cbr = cbuf_new()) == NULL){
|
||||||
goto done;
|
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");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -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,9 +1825,16 @@ 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){
|
||||||
clicon_err(OE_UNIX, errno, "strdup");
|
if ((cbr = cbuf_new()) == NULL){
|
||||||
goto done;
|
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");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue