* Restconf stream notification support - two variants.

* Both a "native" stream support and one using nginx/nchan pub/sub.
  * See (apps/restconf/README.md) for details.
* clixon-config YAML file has new revision: 2018-10-21.
This commit is contained in:
Olof hagsand 2018-10-21 22:19:38 +02:00
parent a4e29bcdb7
commit 71eddeaa74
21 changed files with 811 additions and 144 deletions

View file

@ -21,17 +21,21 @@
* Option CLICON_MODULE_SET_ID is set and changed when modules change. * Option CLICON_MODULE_SET_ID is set and changed when modules change.
* Notification not supported * Notification not supported
* Yang 1.1 notification support (RFC 7950: Sec 7.16) * Yang 1.1 notification support (RFC 7950: Sec 7.16)
* Major rewrite of event streams * Restconf stream notification support - two variants.
* Both a "native" stream support and one using nginx/nchan pub/sub.
* See (apps/restconf/README.md) for details.
* New event streams implementation
* See clicon_stream.[ch] for details * See clicon_stream.[ch] for details
* Added stream discovery according to RFC 5277 for netconf and RFC 8040 for restconf * Added stream discovery according to RFC 5277 for netconf and RFC 8040 for restconf
* Enabled by CLICON_STREAM_DISCOVERY_RFC5277 and CLICON_STREAM_DISCOVERY_RFC8040. * Enabled by CLICON_STREAM_DISCOVERY_RFC5277 and CLICON_STREAM_DISCOVERY_RFC8040.
* Set access/subscribe base URL with: CLICON_STREAM_URL_PREFIX (default https://localhost/streams). * Set access/subscribe base URL with: CLICON_STREAM_URL (default "https://localhost") and CLICON_STREAM_PATH (default "streams")
* Example: new stream "foo" will get access URL: https://localhost/streams/foo * Example: new stream "foo" will get access URL: https://localhost/streams/foo
* Optional pub/sub support enabled by ./configure --enable-publish * Optional pub/sub support enabled by ./configure --enable-publish
* Set publish URL base with: CLICON_STREAM_PUB_PREFIX (default http://localhost/pub) * Set publish URL base with: CLICON_STREAM_PUB (default http://localhost/pub)
* Example: new stream "foo" will get pub URL: https://localhost/pub/foo * Example: new stream "foo" will get pub URL: https://localhost/pub/foo
### API changes on existing features (you may need to change your code) ### API changes on existing features (you may need to change your code)
* clixon-config YAML file has new revision: 2018-10-21.
* Netconf hello capability updated to YANG 1.1 RFC7950 Sec 5.6.4 * Netconf hello capability updated to YANG 1.1 RFC7950 Sec 5.6.4
* Added urn:ietf:params:netconf:capability:yang-library:1.0 * Added urn:ietf:params:netconf:capability:yang-library:1.0
* Thanks @SCadilhac for helping out, see https://github.com/clicon/clixon/issues/39 * Thanks @SCadilhac for helping out, see https://github.com/clicon/clixon/issues/39

View file

@ -140,11 +140,11 @@ run with NGINX.
The implementatation is based on [RFC 8040: RESTCONF Protocol](https://tools.ietf.org/html/rfc8040). The implementatation is based on [RFC 8040: RESTCONF Protocol](https://tools.ietf.org/html/rfc8040).
The following features are supported: The following features are supported:
- OPTIONS, HEAD, GET, POST, PUT, DELETE - OPTIONS, HEAD, GET, POST, PUT, DELETE
- stream notifications
The following are not implemented The following are not implemented
- PATCH - PATCH
- query parameters (section 4.9) - query parameters (section 4.9)
- notifications (sec 6)
- schema resource
See [more detailed instructions](apps/restconf/README.md). See [more detailed instructions](apps/restconf/README.md).
@ -183,7 +183,6 @@ according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341), at
least a subset of the functionality. See more information here: least a subset of the functionality. See more information here:
[NACM](README_NACM.md). [NACM](README_NACM.md).
Runtime Runtime
======= =======

View file

@ -850,6 +850,11 @@ from_client_create_subscription(clicon_handle h,
goto done; goto done;
} }
} }
if ((stream_find(h, stream)) == NULL){
if (netconf_invalid_value(cbret, "application", "No such stream") < 0)
goto done;
goto ok;
}
if (stream_cb_add(h, stream, selector, ce_event_cb, (void*)ce) < 0) if (stream_cb_add(h, stream, selector, ce_event_cb, (void*)ce) < 0)
goto done; goto done;
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>"); cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");

View file

@ -728,6 +728,14 @@ netconf_discard_changes(clicon_handle h,
<severity>major</severity> <severity>major</severity>
</event> </event>
</notification> </notification>
* @see rfc5277:
* An event notification is sent to the client who initiated a
* <create-subscription> command asynchronously when an event of
* interest...
* Parameters: eventTime type dateTime and compliant to [RFC3339]
* Also contains notification-specific tagged content, if any. With
* the exception of <eventTime>, the content of the notification is
* beyond the scope of this document.
*/ */
static int static int
netconf_notification_cb(int s, netconf_notification_cb(int s,
@ -822,6 +830,8 @@ netconf_create_subscription(clicon_handle h,
} }
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, &s) < 0) if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, &s) < 0)
goto done; goto done;
if (xpath_first(*xret, "rpc-reply/rpc-error") != NULL)
goto ok;
if (event_reg_fd(s, if (event_reg_fd(s,
netconf_notification_cb, netconf_notification_cb,
NULL, NULL,

View file

@ -22,14 +22,9 @@ server {
fastcgi_pass unix:/www-data/fastcgi_restconf.sock; fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
include fastcgi_params; include fastcgi_params;
} }
location /stream { # for restconf notifications
fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
include fastcgi_params;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
} }
``` ```
Start nginx daemon Start nginx daemon
``` ```
sudo /etc/init.d nginx start sudo /etc/init.d nginx start
@ -73,12 +68,77 @@ olof@vandal> curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=et
curl -sX POST -d '{"interfaces":{"interface":{"name":"eth1","type":"eth","enabled":"true"}}}' http://localhost/restconf/data curl -sX POST -d '{"interfaces":{"interface":{"name":"eth1","type":"eth","enabled":"true"}}}' http://localhost/restconf/data
``` ```
### Nginx Nchan for streams
Restconf notification event streams needs a server-side push ### Event streams
package. Clixon has used Nchan (nchan.io) for this
Download and install nchan, see nchan.io, Install section. Clixon have two experimental restconf event stream implementations following
RFC8040 Section 6 using SSE. One native and one using Nginx
nchan. The two variants to subscribe to the stream is described in the
next section.
The example [../../example/README.md] creates and EXAMPLE stream.
Set the Clixon configuration options if they differ from default values - if they are OK you do not need to modify them:
```
<CLICON_STREAM_PATH>streams</CLICON_STREAM_PATH>
<CLICON_STREAM_URL>https://example.com</CLICON_STREAM_URL>
<CLICON_STREAM_PUB>http://localhost/pub</CLICON_STREAM_PUB>
```
where
- https://example.com/streams is the public fronting subscription base URL. A specific stream NAME can be accessed as https://example.com/streams/NAME
- http://localhost/pub is the local internal base publish stream.
You access the streams using curl, but they differ slightly in behaviour as described in the following two sections.
### Native event streams
Add the following to extend the nginx configuration file with the following statements:
```
location /streams {
fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
include fastcgi_params;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
```
You access a native stream as follos:
```
curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE
curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE?start-time=2014-10-25T10%3A02%3A00Z&stop-time=2014-10-25T12%3A31%3A00Z
```
where the first command retrieves only new notifications, and the second receives a range of messages.
### Nginx Nchan streams
Nginx uses pub/sub channels and can be configured in a variety of
ways. The following uses a simple variant with one generic subscription
channel (streams) and one publication channel (pub).
Configure clixon with `--enable-publish` which enables curl code for publishing streams to nchan.
Download and install nchan, see (https://nchan.io/#install).
Add the following to extend the nginx configuration file with the following statements:
```
location ~ /streams/(\w+)$ {
nchan_subscriber;
nchan_channel_id $1; #first capture of the location match
}
location ~ /pub/(\w+)$ {
nchan_publisher;
nchan_channel_id $1; #first capture of the location match
}
```
Access the event stream EXAMPLE using curl:
```
curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE
curl -H "Accept: text/event-stream" -H "Last-Event-ID: 1539961709:0" -s -X GET http://localhost/streams/EXAMPLE
```
where the first command retrieves the whole stream history, and the second only retreives the most recent messages given by the ID.
See (https://nchan.io/#eventsource) on more info on how to access an SSE sub endpoint.
### Debugging ### Debugging

View file

@ -221,6 +221,26 @@ notfound(FCGX_Request *r)
return 0; return 0;
} }
/*! HTTP error 406 Not acceptable
* @param[in] r Fastcgi request handle
*/
int
notacceptable(FCGX_Request *r)
{
char *path;
clicon_debug(1, "%s", __FUNCTION__);
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
FCGX_FPrintF(r->out, "Status: 406\r\n"); /* 406 not acceptible */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "<h1>Not Acceptable</h1>\n");
FCGX_FPrintF(r->out, "Not Acceptable\n");
FCGX_FPrintF(r->out, "The target resource does not have a current representation that would be acceptable to the user agent.\n",
path);
return 0;
}
/*! HTTP error 409 /*! HTTP error 409
* @param[in] r Fastcgi request handle * @param[in] r Fastcgi request handle
*/ */

View file

@ -40,7 +40,6 @@
* Constants * Constants
*/ */
#define RESTCONF_API "restconf" #define RESTCONF_API "restconf"
#define RESTCONF_STREAM "stream"
/* /*
* Prototypes (also in clixon_restconf.h) * Prototypes (also in clixon_restconf.h)
@ -52,6 +51,7 @@ int badrequest(FCGX_Request *r);
int unauthorized(FCGX_Request *r); int unauthorized(FCGX_Request *r);
int forbidden(FCGX_Request *r); int forbidden(FCGX_Request *r);
int notfound(FCGX_Request *r); int notfound(FCGX_Request *r);
int notacceptable(FCGX_Request *r);
int conflict(FCGX_Request *r); int conflict(FCGX_Request *r);
int internal_server_error(FCGX_Request *r); int internal_server_error(FCGX_Request *r);
int notimplemented(FCGX_Request *r); int notimplemented(FCGX_Request *r);

View file

@ -529,6 +529,7 @@ main(int argc,
yang_spec *yspec = NULL; yang_spec *yspec = NULL;
yang_spec *yspecfg = NULL; /* For config XXX clixon bug */ yang_spec *yspecfg = NULL; /* For config XXX clixon bug */
char *yang_filename = NULL; char *yang_filename = NULL;
char *stream_path;
/* In the startup, logs to stderr & debug flag set later */ /* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, logdst); clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
@ -561,7 +562,6 @@ main(int argc,
goto done; goto done;
break; break;
} /* switch getopt */ } /* switch getopt */
/* /*
* Logs, error and debug to stderr or syslog, set debug level * Logs, error and debug to stderr or syslog, set debug level
*/ */
@ -583,7 +583,7 @@ main(int argc,
/* Find and read configfile */ /* Find and read configfile */
if (clicon_options_main(h, yspecfg) < 0) if (clicon_options_main(h, yspecfg) < 0)
goto done; goto done;
stream_path = clicon_option_str(h, "CLICON_STREAM_PATH");
/* Now rest of options, some overwrite option file */ /* Now rest of options, some overwrite option file */
optind = 1; optind = 1;
opterr = 0; opterr = 0;
@ -652,10 +652,6 @@ main(int argc,
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") && if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") &&
yang_spec_parse_module(h, "ietf-netconf-notification", CLIXON_DATADIR, NULL, yspec, NULL)< 0) yang_spec_parse_module(h, "ietf-netconf-notification", CLIXON_DATADIR, NULL, yspec, NULL)< 0)
goto done; goto done;
if (stream_register(h, "NETCONF", "default NETCONF event stream") < 0)
goto done;
/* Call start function in all plugins before we go interactive /* Call start function in all plugins before we go interactive
Pass all args after the standard options to plugin_start Pass all args after the standard options to plugin_start
*/ */
@ -692,8 +688,8 @@ main(int argc,
clicon_debug(1, "path: %s", path); clicon_debug(1, "path: %s", path);
if (strncmp(path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0) if (strncmp(path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0)
api_restconf(h, r); /* This is the function */ api_restconf(h, r); /* This is the function */
else if (strncmp(path, "/" RESTCONF_STREAM, strlen("/" RESTCONF_STREAM)) == 0) { else if (strncmp(path+1, stream_path, strlen(stream_path)) == 0) {
api_stream(h, r); api_stream(h, r, stream_path);
} }
else if (strncmp(path, RESTCONF_WELL_KNOWN, strlen(RESTCONF_WELL_KNOWN)) == 0) { else if (strncmp(path, RESTCONF_WELL_KNOWN, strlen(RESTCONF_WELL_KNOWN)) == 0) {
api_well_known(h, r); /* */ api_well_known(h, r); /* */

View file

@ -35,6 +35,26 @@
See RFC 8040 RESTCONF Protocol See RFC 8040 RESTCONF Protocol
Sections 3.8, 6, 9.3 Sections 3.8, 6, 9.3
RFC8040:
A RESTCONF server MAY send the "retry" field, and if it does, RESTCONF
clients SHOULD use it. A RESTCONF server SHOULD NOT send the "event"
or "id" fields, as there are no meaningful values. RESTCONF
servers that do not send the "id" field also do not need to support
the HTTP header field "Last-Event-ID"
The RESTCONF client can then use this URL value to start monitoring
the event stream:
GET /streams/NETCONF HTTP/1.1
Host: example.com
Accept: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
The server MAY support the "start-time", "stop-time", and "filter"
query parameters, defined in Section 4.8. Refer to Appendix B.3.6
for filter parameter examples.
*/ */
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H
@ -62,31 +82,136 @@
/* clicon */ /* clicon */
#include <clixon/clixon.h> #include <clixon/clixon.h>
#include <fcgi_stdio.h> /* Need to be after clixon_xml-h due to attribute format */ #include <fcgi_stdio.h> /* Need to be after clixon_xml.h due to attribute format */
#include "restconf_lib.h"
#include "restconf_stream.h"
/*! Callback when stream notifications arrive from backend
*/
static int static int
restconf_stream(clicon_handle h, restconf_stream_cb(int s,
FCGX_Request *r, void *arg)
event_stream_t *es)
{ {
int retval = -1; int retval = -1;
FCGX_Request *r = (FCGX_Request *)arg;
int eof;
struct clicon_msg *reply = NULL;
cxobj *xtop = NULL; /* top xml */
cxobj *xn; /* notification xml */
cbuf *cb;
int pretty = 0; /* XXX should be via arg */
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
/* get msg (this is the reason this function is called) */
if (clicon_msg_rcv(s, &reply, &eof) < 0){
clicon_debug(1, "%s msg_rcv error", __FUNCTION__);
goto done;
}
clicon_debug(1, "%s msg: %s", __FUNCTION__, reply->op_body);
/* handle close from remote end: this will exit the client */
if (eof){
clicon_debug(1, "%s eof", __FUNCTION__);
clicon_err(OE_PROTO, ESHUTDOWN, "Socket unexpected close");
close(s);
errno = ESHUTDOWN;
event_unreg_fd(s, restconf_stream_cb);
FCGX_FPrintF(r->out, "SHUTDOWN\r\n");
FCGX_FPrintF(r->out, "\r\n");
FCGX_FFlush(r->out);
clicon_exit_set();
goto done;
}
if (clicon_msg_decode(reply, &xtop) < 0)
goto done;
/* create event */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_PLUGIN, errno, "cbuf_new");
goto done;
}
if ((xn = xpath_first(xtop, "notification")) == NULL)
goto ok;
#ifdef notused
xt = xpath_first(xn, "eventTime");
if ((xe = xpath_first(xn, "event")) == NULL) /* event can depend on yang? */
goto ok;
if (xt)
FCGX_FPrintF(r->out, "M#id: %s\r\n", xml_body(xt));
else{ /* XXX */
gettimeofday(&tv, NULL);
FCGX_FPrintF(r->out, "M#id: %02d:0\r\n", tv.tv_sec);
}
#endif
if (clicon_xml2cbuf(cb, xn, 0, pretty) < 0)
goto done;
FCGX_FPrintF(r->out, "data: %s\r\n", cbuf_get(cb));
FCGX_FPrintF(r->out, "\r\n");
FCGX_FFlush(r->out);
ok:
retval = 0;
done:
clicon_debug(1, "%s retval: %d", __FUNCTION__, retval);
if (xtop != NULL)
xml_free(xtop);
if (reply)
free(reply);
if (cb)
cbuf_free(cb);
return retval;
}
/*! Send subsctription to backend
* @param[in] h Clicon handle
* @param[in] r Fastcgi request handle
* @param[in] name Stream name
* @param[out] sp Socket -1 if not set
*/
static int
restconf_stream(clicon_handle h,
FCGX_Request *r,
char *name,
int pretty,
int use_xml,
int *sp)
{
int retval = -1;
cxobj *xret = NULL;
cxobj *xe;
cbuf *cb = NULL;
int s; /* socket */
*sp = -1;
clicon_debug(1, "%s", __FUNCTION__);
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
cprintf(cb, "<rpc><create-subscription><stream>%s</stream></create-subscription></rpc>]]>]]>", name);
if (clicon_rpc_netconf(h, cbuf_get(cb), &xret, &s) < 0)
goto done;
if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){
if (api_return_err(h, r, xe, pretty, use_xml) < 0)
goto done;
goto ok;
}
/* Setting up stream */
FCGX_SetExitStatus(201, r->out); /* Created */ FCGX_SetExitStatus(201, r->out); /* Created */
FCGX_FPrintF(r->out, "Content-Type: text/event-stream\r\n"); FCGX_FPrintF(r->out, "Content-Type: text/event-stream\r\n");
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n"); FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
FCGX_FPrintF(r->out, "Connection: keep-alive\r\n"); FCGX_FPrintF(r->out, "Connection: keep-alive\r\n");
FCGX_FPrintF(r->out, "X-Accel-Buffering: no\r\n"); FCGX_FPrintF(r->out, "X-Accel-Buffering: no\r\n");
FCGX_FPrintF(r->out, "\r\n"); FCGX_FPrintF(r->out, "\r\n");
FCGX_FPrintF(r->out, "Here is output\r\n");
FCGX_FPrintF(r->out, "\r\n");
FCGX_FFlush(r->out); FCGX_FFlush(r->out);
sync(); *sp = s;
sleep(1); ok:
FCGX_FPrintF(r->out, "Here is output 2\r\n");
retval = 0; retval = 0;
// done: done:
if (xret)
xml_free(xret);
if (cb)
cbuf_free(cb);
return retval; return retval;
} }
@ -94,12 +219,33 @@ restconf_stream(clicon_handle h,
#include "restconf_lib.h" #include "restconf_lib.h"
#include "restconf_stream.h" #include "restconf_stream.h"
int
stream_timeout(int s,
void *arg)
{
struct timeval t;
struct timeval t1;
FCGX_Request *r = (FCGX_Request *)arg;
clicon_debug(1, "%s", __FUNCTION__);
if (FCGX_GetError(r->out) != 0) /* break loop */
clicon_exit_set();
else{
gettimeofday(&t, NULL);
t1.tv_sec = 1; t1.tv_usec = 0;
timeradd(&t, &t1, &t);
event_reg_timeout(t, stream_timeout, arg, "Stream timeout");
}
return 0;
}
/*! Process a FastCGI request /*! Process a FastCGI request
* @param[in] r Fastcgi request handle * @param[in] r Fastcgi request handle
*/ */
int int
api_stream(clicon_handle h, api_stream(clicon_handle h,
FCGX_Request *r) FCGX_Request *r,
char *streampath)
{ {
int retval = -1; int retval = -1;
char *path; char *path;
@ -113,28 +259,18 @@ api_stream(clicon_handle h,
cbuf *cb = NULL; cbuf *cb = NULL;
char *data; char *data;
int authenticated = 0; int authenticated = 0;
char *media_accept;
char *media_content_type;
int pretty; int pretty;
int parse_xml = 0; /* By default expect and parse JSON */ int use_xml = 1; /* default */
int use_xml = 0; /* By default use JSON */
cbuf *cbret = NULL; cbuf *cbret = NULL;
cxobj *xret = NULL; cxobj *xret = NULL;
cxobj *xerr; cxobj *xerr;
event_stream_t *es; int s=-1;
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
path = FCGX_GetParam("REQUEST_URI", r->envp); path = FCGX_GetParam("REQUEST_URI", r->envp);
query = FCGX_GetParam("QUERY_STRING", r->envp); query = FCGX_GetParam("QUERY_STRING", r->envp);
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
/* get xml/json in put and output */ test(r, 1);
media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp);
if (media_accept && strcmp(media_accept, "application/yang-data+xml")==0)
use_xml++;
media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp);
if (media_content_type &&
strcmp(media_content_type, "application/yang-data+xml")==0)
parse_xml++;
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL) if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
goto done; goto done;
/* Sanity check of path. Should be /stream/<name> */ /* Sanity check of path. Should be /stream/<name> */
@ -146,11 +282,11 @@ api_stream(clicon_handle h,
retval = notfound(r); retval = notfound(r);
goto done; goto done;
} }
if (strcmp(pvec[1], RESTCONF_STREAM)){ if (strcmp(pvec[1], streampath)){
retval = notfound(r); retval = notfound(r);
goto done; goto done;
} }
test(r, 1);
if ((method = pvec[2]) == NULL){ if ((method = pvec[2]) == NULL){
retval = notfound(r); retval = notfound(r);
goto done; goto done;
@ -158,6 +294,7 @@ api_stream(clicon_handle h,
clicon_debug(1, "%s: method=%s", __FUNCTION__, method); clicon_debug(1, "%s: method=%s", __FUNCTION__, method);
if (str2cvec(query, '&', '=', &qvec) < 0) if (str2cvec(query, '&', '=', &qvec) < 0)
goto done; goto done;
if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */ if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */
goto done; goto done;
/* data */ /* data */
@ -167,7 +304,6 @@ api_stream(clicon_handle h,
clicon_debug(1, "%s DATA=%s", __FUNCTION__, data); clicon_debug(1, "%s DATA=%s", __FUNCTION__, data);
if (str2cvec(data, '&', '=', &dvec) < 0) if (str2cvec(data, '&', '=', &dvec) < 0)
goto done; goto done;
/* If present, check credentials. See "plugin_credentials" in plugin /* If present, check credentials. See "plugin_credentials" in plugin
* See RFC 8040 section 2.5 * See RFC 8040 section 2.5
*/ */
@ -191,12 +327,22 @@ api_stream(clicon_handle h,
goto ok; goto ok;
} }
clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h)); clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
if ((es = stream_find(h, method)) == NULL){ if (restconf_stream(h, r, method, pretty, use_xml, &s) < 0)
retval = notfound(r);
goto done; goto done;
if (s != -1){
/* Listen to backend socket */
if (event_reg_fd(s,
restconf_stream_cb,
(void*)r,
"stream socket") < 0)
goto done;
/* Poll upstream errors */
stream_timeout(0, (void*)r);
/* Start loop */
event_loop();
event_unreg_fd(s, restconf_stream_cb);
clicon_exit_reset();
} }
if (restconf_stream(h, r, es) < 0)
goto done;
ok: ok:
retval = 0; retval = 0;
done: done:

View file

@ -39,6 +39,6 @@
/* /*
* Prototypes * Prototypes
*/ */
int api_stream(clicon_handle h, FCGX_Request *r); int api_stream(clicon_handle h, FCGX_Request *r, char *streampath);
#endif /* _RESTCONF_STREAM_H_ */ #endif /* _RESTCONF_STREAM_H_ */

View file

@ -180,20 +180,25 @@ Example:
## How do I use notifications? ## How do I use notifications?
The example has a prebuilt notification stream called "NETCONF" that triggers every 5s. The example has a prebuilt notification stream called "EXAMPLE" that triggers every 5s.
You enable the notification either via the cli: You enable the notification via the CLI:
``` ```
cli> notify cli> notify
cli> cli>
``` ```
or via netconf: or via NETCONF:
``` ```
clixon_netconf -qf /usr/local/etc/example.xml clixon_netconf -qf /usr/local/etc/example.xml
<rpc><create-subscription><stream>NETCONF</stream></create-subscription></rpc>]]>]]> <rpc><create-subscription><stream>EXAMPLE</stream></create-subscription></rpc>]]>]]>
<rpc-reply><ok/></rpc-reply>]]>]]> <rpc-reply><ok/></rpc-reply>]]>]]>
<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2018-09-30T12:44:59.657276</eventTime><event xmlns="http://example.com/event/1.0"><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>]]>]]> <notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2018-09-30T12:44:59.657276</eventTime><event xmlns="http://example.com/event/1.0"><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>]]>]]>
... ...
``` ```
or via restconf:
```
curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE
```
Consult (../apps/restconf/README.md) on more information on how to setup a reverse proxy for restconf streams. It is also possible to configure a pub/sub system such as (Nginx Nchan)[https://nchan.io].
## How should I start the backend daemon? ## How should I start the backend daemon?

View file

@ -76,10 +76,10 @@ Send restconf command
<rpc><validate><source><candidate/></source></validate></rpc>]]>]]> <rpc><validate><source><candidate/></source></validate></rpc>]]>]]>
``` ```
## Creating notification ## Streams
The example has an example notification triggering every 10s. To start a notification The example has an EXAMPLE stream notification triggering every 5s. To start a notification
stream in the session, create a subscription: stream in the session using netconf, create a subscription:
``` ```
<rpc><create-subscription><stream>ROUTING</stream></create-subscription></rpc>]]>]]> <rpc><create-subscription><stream>ROUTING</stream></create-subscription></rpc>]]>]]>
<rpc-reply><ok/></rpc-reply>]]>]]> <rpc-reply><ok/></rpc-reply>]]>]]>
@ -95,6 +95,8 @@ Routing notification
... ...
``` ```
Restconf support is also supported, see [../apps/restconf/README.md].
## Initializing a plugin ## Initializing a plugin
The example includes a restonf, netconf, CLI and two backend plugins. The example includes a restonf, netconf, CLI and two backend plugins.

View file

@ -43,6 +43,8 @@
*/ */
int clicon_exit_set(void); int clicon_exit_set(void);
int clicon_exit_reset(void);
int clicon_exit_get(void); int clicon_exit_get(void);
int event_reg_fd(int fd, int (*fn)(int, void*), void *arg, char *str); int event_reg_fd(int fd, int (*fn)(int, void*), void *arg, char *str);

View file

@ -75,6 +75,7 @@ struct event_data{
/* /*
* Internal variables * Internal variables
* XXX consider use handle variables instead of global
*/ */
static struct event_data *ee = NULL; static struct event_data *ee = NULL;
static struct event_data *ee_timers = NULL; static struct event_data *ee_timers = NULL;
@ -86,7 +87,7 @@ static int _clicon_exit = 0;
/*! For signal handlers: instead of doing exit, set a global variable to exit /*! For signal handlers: instead of doing exit, set a global variable to exit
* Status is then checked in event_loop. * Status is then checked in event_loop.
* Note it maybe would be better to do use on a handle basis, bit a signal * Note it maybe would be better to do use on a handle basis, but a signal
* handler is global * handler is global
*/ */
int int
@ -96,6 +97,15 @@ clicon_exit_set(void)
return 0; return 0;
} }
/*! Set exit to 0
*/
int
clicon_exit_reset(void)
{
_clicon_exit = 0;
return 0;
}
/*! Get the status of global exit variable, usually set by signal handlers /*! Get the status of global exit variable, usually set by signal handlers
*/ */
int int
@ -274,6 +284,8 @@ event_poll(int fd)
/*! Dispatch file descriptor events (and timeouts) by invoking callbacks. /*! Dispatch file descriptor events (and timeouts) by invoking callbacks.
* There is an issue with fairness that timeouts may take over all events * There is an issue with fairness that timeouts may take over all events
* One could try to poll the file descriptors after a timeout? * One could try to poll the file descriptors after a timeout?
* @retval 0 OK
* @retval -1 Error: eg select, callback, timer,
*/ */
int int
event_loop(void) event_loop(void)

View file

@ -151,8 +151,14 @@ clicon_rpc_msg(clicon_handle h,
* Want to go over to use netconf directly between client and server,... * Want to go over to use netconf directly between client and server,...
* @param[in] h clicon handle * @param[in] h clicon handle
* @param[in] xmlstr XML netconf tree as string * @param[in] xmlstr XML netconf tree as string
* @param[out] xret Return XML netconf tree, error or OK * @param[out] xret Return XML netconf tree, error or OK (need to be freed)
* @param[out] sp Socket pointer for notification, otherwise NULL * @param[out] sp Socket pointer for notification, otherwise NULL
* @code
* cxobj *xret = NULL;
* if (clicon_rpc_netconf(h, "<rpc></rpc>", &xret, NULL) < 0)
* err;
* xml_free(xret);
* @endcode
* @see clicon_rpc_netconf_xml xml as tree instead of string * @see clicon_rpc_netconf_xml xml as tree instead of string
*/ */
int int
@ -181,6 +187,14 @@ clicon_rpc_netconf(clicon_handle h,
* @param[in] xml XML netconf tree * @param[in] xml XML netconf tree
* @param[out] xret Return XML netconf tree, error or OK * @param[out] xret Return XML netconf tree, error or OK
* @param[out] sp Socket pointer for notification, otherwise NULL * @param[out] sp Socket pointer for notification, otherwise NULL
* @code
* cxobj *xret = NULL;
* int s;
* if (clicon_rpc_netconf_xml(h, x, &xret, &s) < 0)
* err;
* xml_free(xret);
* @endcode
* @see clicon_rpc_netconf xml as string instead of tree * @see clicon_rpc_netconf xml as string instead of tree
*/ */
int int

View file

@ -149,6 +149,7 @@ stream_get_xml(clicon_handle h,
{ {
event_stream_t *es = NULL; event_stream_t *es = NULL;
char *url_prefix; char *url_prefix;
char *stream_path;
cprintf(cb, "<streams>"); cprintf(cb, "<streams>");
for (es=clicon_stream(h); es; es=es->es_next){ for (es=clicon_stream(h); es; es=es->es_next){
@ -160,9 +161,10 @@ stream_get_xml(clicon_handle h,
if (access){ if (access){
cprintf(cb, "<access>"); cprintf(cb, "<access>");
cprintf(cb, "<encoding>xml</encoding>"); cprintf(cb, "<encoding>xml</encoding>");
url_prefix = clicon_option_str(h, "CLICON_STREAM_URL_PREFIX"); url_prefix = clicon_option_str(h, "CLICON_STREAM_URL");
cprintf(cb, "<location>%s/%s</location>", stream_path = clicon_option_str(h, "CLICON_STREAM_PATH");
url_prefix, es->es_name); cprintf(cb, "<location>%s/%s/%s</location>",
url_prefix, stream_path, es->es_name);
cprintf(cb, "</access>"); cprintf(cb, "</access>");
} }
cprintf(cb, "</stream>"); cprintf(cb, "</stream>");
@ -190,8 +192,7 @@ stream_del()
* @param[in] fn Callback when event occurs * @param[in] fn Callback when event occurs
* @param[in] arg Argument to use with callback. Also handle when deleting * @param[in] arg Argument to use with callback. Also handle when deleting
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error, ie no such stream
* XXX: from subscription_add and client_subscription_add
*/ */
int int
stream_cb_add(clicon_handle h, stream_cb_add(clicon_handle h,
@ -502,8 +503,8 @@ stream_publish_cb(clicon_handle h,
clicon_err(OE_XML, errno, "cbuf_new"); clicon_err(OE_XML, errno, "cbuf_new");
goto done; goto done;
} }
if ((pub_prefix = clicon_option_str(h, "CLICON_STREAM_PUB_PREFIX")) == NULL){ if ((pub_prefix = clicon_option_str(h, "CLICON_STREAM_PUB")) == NULL){
clicon_err(OE_CFG, ENOENT, "CLICON_STREAM_PUB_PREFIX not defined"); clicon_err(OE_CFG, ENOENT, "CLICON_STREAM_PUB not defined");
goto done; goto done;
} }

View file

@ -176,24 +176,24 @@ expectwait(){
input=$2 input=$2
expect=$3 expect=$3
wait=$4 wait=$4
# Do while read stuff # Do while read stuff
echo timeout > /tmp/flag echo timeout > /tmp/flag
ret="" ret=""
sleep $wait | cat <(echo $input) -| $cmd | while [ 1 ] ; do sleep $wait | cat <(echo $input) -| $cmd | while [ 1 ] ; do
read r read -t 20 r
# echo "r:$r" # echo "r:$r"
ret="$ret$r" ret="$ret$r"
match=$(echo "$ret" | grep -Eo "$expect"); match=$(echo "$ret" | grep -Eo "$expect");
if [ -z "$match" ]; then if [ -z "$match" ]; then
echo error > /tmp/flag echo error > /tmp/flag
err $expect "$ret" err "$expect" "$ret"
else else
echo ok > /tmp/flag # only this is OK echo ok > /tmp/flag # only this is OK
break; break;
fi fi
done done
cat /tmp/flag # cat /tmp/flag
if [ $(cat /tmp/flag) != "ok" ]; then if [ $(cat /tmp/flag) != "ok" ]; then
cat /tmp/flag cat /tmp/flag
exit exit

View file

@ -32,6 +32,9 @@ cat <<EOF > $cfg
<CLICON_MODULE_LIBRARY_RFC7895>true</CLICON_MODULE_LIBRARY_RFC7895> <CLICON_MODULE_LIBRARY_RFC7895>true</CLICON_MODULE_LIBRARY_RFC7895>
<CLICON_STREAM_DISCOVERY_RFC5277>true</CLICON_STREAM_DISCOVERY_RFC5277> <CLICON_STREAM_DISCOVERY_RFC5277>true</CLICON_STREAM_DISCOVERY_RFC5277>
<CLICON_STREAM_DISCOVERY_RFC8040>true</CLICON_STREAM_DISCOVERY_RFC8040> <CLICON_STREAM_DISCOVERY_RFC8040>true</CLICON_STREAM_DISCOVERY_RFC8040>
<CLICON_STREAM_PATH>streams</CLICON_STREAM_PATH>
<CLICON_STREAM_URL>https://localhost</CLICON_STREAM_URL>
<CLICON_STREAM_PUB>http://localhost/pub</CLICON_STREAM_PUB>
</config> </config>
EOF EOF
@ -109,7 +112,33 @@ expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring
new "restconf subscribe RFC8040 Sec 6.3, get location" new "restconf subscribe RFC8040 Sec 6.3, get location"
expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams/stream=EXAMPLE/access=xml/location" 0 '{"location": "https://localhost/streams/EXAMPLE"}' expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams/stream=EXAMPLE/access=xml/location" 0 '{"location": "https://localhost/streams/EXAMPLE"}'
# Restconf stream subscription RFC8040 Sec 6.3 - Native solution
new "restconf monitor event nonexist stream"
expectwait 'curl -s -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" http://localhost/streams/NOTEXIST' 0 '<errors xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><error><error-tag>invalid-value</error-tag><error-type>application</error-type><error-severity>error</error-severity><error-message>No such stream</error-message></error></errors>' 2
# Need manual testing
new "restconf monitor streams native NEEDS manual testing"
if false; then if false; then
# url -H "Accept: text/event-stream" http://localhost/streams/EXAMPLE
# Expect:
# data: <notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2018-10-21T19:22:11.381827</eventTime><event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>
#
# data: <notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2018-10-21T19:22:16.387228</eventTime><event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>
new "restconf monitor event ok stream"
expectwait 'curl -s -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" http://localhost/streams/EXAMPLE' 0 'foo' 2
fi
# Restconf stream subscription RFC8040 Sec 6.3 - Nginx nchan solution
# Need manual testing
new "restconf monitor streams nchan NEEDS manual testing"
if false; then
# url -H "Accept: text/event-stream" http://localhost/streams/EXAMPLE
# Expect:
echo foo
fi
# Netconf stream subscription
# Switch here since subscriptions takes time
if true; then
new "netconf EXAMPLE subscription" new "netconf EXAMPLE subscription"
expectwait "$clixon_netconf -qf $cfg -y $fyang" '<rpc><create-subscription><stream>EXAMPLE</stream></create-subscription></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]><notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>20' 5 expectwait "$clixon_netconf -qf $cfg -y $fyang" '<rpc><create-subscription><stream>EXAMPLE</stream></create-subscription></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]><notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>20' 5
@ -118,10 +147,10 @@ expectwait "$clixon_netconf -qf $cfg -y $fyang" "<rpc><create-subscription><stre
new "netconf EXAMPLE subscription with filter classifier" new "netconf EXAMPLE subscription with filter classifier"
expectwait "$clixon_netconf -qf $cfg -y $fyang" "<rpc><create-subscription><stream>EXAMPLE</stream><filter type=\"xpath\" select=\"event[event-class='fault']\"/></create-subscription></rpc>]]>]]>" '^<rpc-reply><ok/></rpc-reply>]]>]]><notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>20' 5 expectwait "$clixon_netconf -qf $cfg -y $fyang" "<rpc><create-subscription><stream>EXAMPLE</stream><filter type=\"xpath\" select=\"event[event-class='fault']\"/></create-subscription></rpc>]]>]]>" '^<rpc-reply><ok/></rpc-reply>]]>]]><notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>20' 5
fi
#new "restconf monitor event stream RFC8040 Sec 6.3" new "netconf NONEXIST subscription"
#expectfn "curl -H \"Accept: text/event-stream\" -s -X GET http://localhost/streams/EXAMPLE" 0 '<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2018-10-14T14:17:50.875370</eventTime><event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>' expectwait "$clixon_netconf -qf $cfg -y $fyang" '<rpc><create-subscription><stream>NONEXIST</stream></create-subscription></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-tag>invalid-value</error-tag><error-type>application</error-type><error-severity>error</error-severity><error-message>No such stream</error-message></rpc-error></rpc-reply>]]>]]>$' 5
fi
new "Kill restconf daemon" new "Kill restconf daemon"
sudo pkill -u www-data clixon_restconf sudo pkill -u www-data clixon_restconf

View file

@ -40,7 +40,7 @@ datarootdir = @datarootdir@
CLIXON_DATADIR = @CLIXON_DATADIR@ CLIXON_DATADIR = @CLIXON_DATADIR@
YANGSPECS = clixon-config@2018-04-30.yang YANGSPECS = clixon-config@2018-10-21.yang
YANGSPECS += ietf-netconf@2011-06-01.yang YANGSPECS += ietf-netconf@2011-06-01.yang
YANGSPECS += ietf-netconf-acm@2018-02-14.yang YANGSPECS += ietf-netconf-acm@2018-02-14.yang
YANGSPECS += ietf-inet-types@2013-07-15.yang YANGSPECS += ietf-inet-types@2013-07-15.yang

View file

@ -38,9 +38,9 @@ module clixon-config {
***** END LICENSE BLOCK *****"; ***** END LICENSE BLOCK *****";
revision 2018-09-30 { revision 2018-04-30 {
description description
"Aligned to Clixon 3.8.0"; "Released with Clixon 3.6";
} }
typedef startup_mode{ typedef startup_mode{
description description
@ -117,16 +117,6 @@ module clixon-config {
} }
} }
container config { container config {
leaf-list CLICON_FEATURE {
description
"Supported features as used by YANG feature/if-feature
value is: <module>:<feature>, where <module> and <feature>
are either names, or the special character '*'.
*:an* means enable all features
<module>:* means enable all features in the specified module
*:<feature> means enable the specific feature in all modules";
type string;
}
leaf CLICON_CONFIGFILE{ leaf CLICON_CONFIGFILE{
type string; type string;
description description
@ -359,54 +349,5 @@ module clixon-config {
type string; type string;
description "RFC8341 NACM external configuration file"; description "RFC8341 NACM external configuration file";
} }
leaf CLICON_MODULE_LIBRARY_RFC7895 {
type boolean;
default true;
description "Enable RFC 7895 YANG Module library support as state
data. If enabled, module info will appear when doing
netconf get or restconf GET";
}
leaf CLICON_MODULE_SET_ID {
type string;
default "0";
description "If RFC 7895 YANG Module library enabled:
Contains a server-specific identifier representing
the current set of modules and submodules. The
server MUST change the value of this leaf if the
information represented by the 'module' list instances
has changed.";
}
leaf CLICON_STREAM_DISCOVERY_RFC5277 {
type boolean;
default false;
description "Enable event stream discovery as described in RFC 5277
sections 3.2. If enabled, available streams will appear
when doing netconf get or restconf GET";
}
leaf CLICON_STREAM_DISCOVERY_RFC8040 {
type boolean;
default false;
description "Enable event stream discovery as described in RFC 5277
sections 3.2. If enabled, available streams will appear
when doing netconf get or restconf GET";
}
leaf CLICON_STREAM_URL_PREFIX {
type string;
default "https://localhost/streams";
description "See RFC 8040 Sec 9.3 location leaf:
'Contains a URL that represents the entry point for
establishing notification delivery via server-sent events.'
Prepend this constant to name of stream.
Example: https://localhost/streams/NETCONF. Note this is the
external URL, not local behind a reverse-proxy";
}
leaf CLICON_STREAM_PUB_PREFIX {
type string;
default "http://localhost/pub";
description "For stream publish using eg nchan, the base address
to publish to.
Example: http://localhost/pub/NETCONF. Note this may
be local URL behind reverse-proxy";
}
} }
} }

View file

@ -0,0 +1,421 @@
module clixon-config {
prefix cc;
organization
"Clicon / Clixon";
contact
"Olof Hagsand <olof@hagsand.se>";
description
"Clixon configuration file
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
This file is part of CLIXON
Licensed under the Apache License, Version 2.0 (the \"License\");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an \"AS IS\" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the \"GPL\"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****";
revision 2018-10-21 {
description
"Released in Clixon 3.8";
}
typedef startup_mode{
description
"Which method to boot/start clicon backend.
The methods differ in how they reach a running state
Which source database to commit from, if any.";
type enumeration{
enum none{
description
"Do not touch running state
Typically after crash when running state and db are synched";
}
enum init{
description
"Initialize running state.
Start with a completely clean running state";
}
enum running{
description
"Commit running db configuration into running state
After reboot if a persistent running db exists";
}
enum startup{
description
"Commit startup configuration into running state
After reboot when no persistent running db exists";
}
}
}
typedef xmldb_format{
description
"Format of TEXT xml database format.";
type enumeration{
enum xml{
description "Save and load xmldb as XML";
}
enum json{
description "Save and load xmldb as JSON";
}
}
}
typedef cli_genmodel_type{
description
"How to generate CLI from YANG model,
eg list a{ key x; leaf x; leaf y;}";
type enumeration{
enum NONE{
description "No extra keywords: a <x> <y>";
}
enum VARS{
description "Keywords on non-key variables: a <x> y <y>";
}
enum ALL{
description "Keywords on all variables: a x <x> y <y>";
}
}
}
typedef nacm_mode{
description
"Mode of RFC8341 Network Configuration Access Control Model.
It is unclear from the RFC whether NACM rules are internal
in a configuration (ie embedded in regular config) or external/OOB
in s separate, specific NACM-config";
type enumeration{
enum disabled{
description "NACM is disabled";
}
enum internal{
description "NACM is enabled and available in the regular config";
}
enum external{
description "NACM is enabled and available in a separate config";
}
}
}
container config {
leaf-list CLICON_FEATURE {
description
"Supported features as used by YANG feature/if-feature
value is: <module>:<feature>, where <module> and <feature>
are either names, or the special character '*'.
*:an* means enable all features
<module>:* means enable all features in the specified module
*:<feature> means enable the specific feature in all modules";
type string;
}
leaf CLICON_CONFIGFILE{
type string;
description
"Location of configuration-file for default values (this file)";
}
leaf CLICON_YANG_DIR {
type string;
mandatory true;
description
"Location of YANG module and submodule files.";
}
leaf CLICON_YANG_MODULE_MAIN {
type string;
default "clicon";
description
"Option used to construct initial yang file:
<module>[@<revision>]";
}
leaf CLICON_YANG_MODULE_REVISION {
type string;
description
"Option used to construct initial yang file:
<module>[@<revision>]";
}
leaf CLICON_BACKEND_DIR {
type string;
description
"Location of backend .so plugins. Load all .so
plugins in this dir as backend plugins";
}
leaf CLICON_BACKEND_REGEXP {
type string;
description
"Regexp of matching backend plugins in CLICON_BACKEND_DIR";
default "(.so)$";
}
leaf CLICON_NETCONF_DIR {
type string;
description "Location of netconf (frontend) .so plugins";
}
leaf CLICON_RESTCONF_DIR {
type string;
description
"Location of restconf (frontend) .so plugins. Load all .so
plugins in this dir as restconf code plugins";
}
leaf CLICON_RESTCONF_PATH {
type string;
default "/www-data/fastcgi_restconf.sock";
description
"FastCGI unix socket. Should be specified in webserver
Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock";
}
leaf CLICON_RESTCONF_PRETTY {
type boolean;
default true;
description
"Restconf return value pretty print.
Restconf clients may add HTTP header:
Accept: application/yang-data+json, or
Accept: application/yang-data+xml
to get return value in XML or JSON.
RFC 8040 examples print XML and JSON in pretty-printed form.
Setting this value to false makes restconf return not pretty-printed
which may be desirable for performance or tests";
}
leaf CLICON_CLI_DIR {
type string;
description
"Location of cli frontend .so plugins. Load all .so
plugins in this dir as CLI object plugins";
}
leaf CLICON_CLISPEC_DIR {
type string;
description
"Location of frontend .cli cligen spec files. Load all .cli
files in this dir as CLI specification files";
}
leaf CLICON_CLISPEC_FILE {
type string;
description "Specific frontend .cli cligen spec file.";
}
leaf CLICON_CLI_MODE {
type string;
default "base";
description
"Startup CLI mode. This should match a CLICON_MODE set in
one of the clispec files";
}
leaf CLICON_CLI_GENMODEL {
type int32;
default 1;
description
"Generate code for CLI completion of existing db symbols.
Example: Add name=\"myspec\" in datamodel spec and reference
as @myspec";
}
leaf CLICON_CLI_GENMODEL_COMPLETION {
type int32;
default 1;
description "Generate code for CLI completion of existing db symbols";
}
leaf CLICON_CLI_GENMODEL_TYPE {
type cli_genmodel_type;
default "VARS";
description "How to generate and show CLI syntax: VARS|ALL";
}
leaf CLICON_CLI_VARONLY {
type int32;
default 1;
description
"Dont include keys in cvec in cli vars callbacks,
ie a & k in 'a <b> k <c>' ignored";
}
leaf CLICON_CLI_LINESCROLLING {
type int32;
default 1;
description
"Set to 0 if you want CLI to wrap to next line.
Set to 1 if you want CLI to scroll sideways when approaching
right margin";
}
leaf CLICON_SOCK_FAMILY {
type string;
default "UNIX";
description
"Address family for communicating with clixon_backend
(UNIX|IPv4|IPv6)";
}
leaf CLICON_SOCK {
type string;
mandatory true;
description
"If family above is AF_UNIX: Unix socket for communicating
with clixon_backend. If family is AF_INET: IPv4 address";
}
leaf CLICON_SOCK_PORT {
type int32;
default 4535;
description
"Inet socket port for communicating with clixon_backend
(only IPv4|IPv6)";
}
leaf CLICON_SOCK_GROUP {
type string;
default "clicon";
description "Group membership to access clixon_backend unix socket";
}
leaf CLICON_BACKEND_PIDFILE {
type string;
mandatory true;
description "Process-id file of backend daemon";
}
leaf CLICON_AUTOCOMMIT {
type int32;
default 0;
description
"Set if all configuration changes are committed automatically
on every edit change. Explicit commit commands unnecessary";
}
leaf CLICON_XMLDB_DIR {
type string;
mandatory true;
description
"Directory where \"running\", \"candidate\" and \"startup\" are placed";
}
leaf CLICON_XMLDB_PLUGIN {
type string;
mandatory true;
description
"XMLDB datastore plugin filename
(see datastore/ and clixon_xml_db.[ch])";
}
leaf CLICON_XMLDB_CACHE {
type boolean;
default true;
description
"XMLDB datastore cache.
If set, XML candidate/running parsed tree is stored in memory
If not set, candidate/running is always accessed via disk.";
}
leaf CLICON_XMLDB_FORMAT {
type xmldb_format;
default xml;
description "XMLDB datastore format.";
}
leaf CLICON_XMLDB_PRETTY {
type boolean;
default true;
description
"XMLDB datastore pretty print.
If set, insert spaces and line-feeds making the XML/JSON human
readable. If not set, make the XML/JSON more compact.";
}
leaf CLICON_XML_SORT {
type boolean;
default true;
description
"If set, sort XML lists and leaf-lists alphabetically and uses binary
search. Unless ordered-by user is used.
Only works for Yang specified XML.
If not set, all lists accessed via linear search.";
}
leaf CLICON_USE_STARTUP_CONFIG {
type int32;
default 0;
description
"Enabled uses \"startup\" configuration on boot. It is called
startup_db and exists in XMLDB_DIR.
NOTE: Obsolete with 1.3.3 and CLICON_STARTUP_MODE";
}
leaf CLICON_STARTUP_MODE {
type startup_mode;
description "Which method to boot/start clicon backend";
}
leaf CLICON_TRANSACTION_MOD {
type boolean;
default false;
description "If set, modifications in validation and commit
callbacks are written back into the datastore";
}
leaf CLICON_NACM_MODE {
type nacm_mode;
default disabled;
description "RFC8341 network access configuration control model
(NACM) mode: disabled, in regular (internal) config
or separate external file given by CLICON_NACM_FILE";
}
leaf CLICON_NACM_FILE {
type string;
description "RFC8341 NACM external configuration file";
}
leaf CLICON_MODULE_LIBRARY_RFC7895 {
type boolean;
default true;
description "Enable RFC 7895 YANG Module library support as state
data. If enabled, module info will appear when doing
netconf get or restconf GET";
}
leaf CLICON_MODULE_SET_ID {
type string;
default "0";
description "If RFC 7895 YANG Module library enabled:
Contains a server-specific identifier representing
the current set of modules and submodules. The
server MUST change the value of this leaf if the
information represented by the 'module' list instances
has changed.";
}
leaf CLICON_STREAM_DISCOVERY_RFC5277 {
type boolean;
default false;
description "Enable event stream discovery as described in RFC 5277
sections 3.2. If enabled, available streams will appear
when doing netconf get or restconf GET";
}
leaf CLICON_STREAM_DISCOVERY_RFC8040 {
type boolean;
default false;
description "Enable event stream discovery as described in RFC 5277
sections 3.2. If enabled, available streams will appear
when doing netconf get or restconf GET";
}
leaf CLICON_STREAM_PATH {
type string;
default "streams";
description "Stream path appended to CLICON_STREAM_URL to form
stream subscription URL.";
}
leaf CLICON_STREAM_URL {
type string;
default "https://localhost";
description "Prepend this to CLICON_STREAM_PATH to form URL.
See RFC 8040 Sec 9.3 location leaf:
'Contains a URL that represents the entry point for
establishing notification delivery via server-sent events.'
Prepend this constant to name of stream.
Example: https://localhost/streams/NETCONF. Note this is the
external URL, not local behind a reverse-proxy.
Note that -s <stream> command-line option to clixon_restconf
should correspond to last path of url (eg 'streams')";
}
leaf CLICON_STREAM_PUB {
type string;
default "http://localhost/pub";
description "For stream publish using eg nchan, the base address
to publish to.
Example: http://localhost/pub/NETCONF. Note this may
be local URL behind reverse-proxy";
}
}
}