* Set access/subscribe base URL with: CLICON_STREAM_URL_PREFIX (default https\

://localhost/streams).
    * Example: new stream "foo" will get access URL: https://localhost/streams/\
foo
  * Optional pub/sub support enabled by ./configure --enable-publish
    * Set publish URL base with: CLICON_STREAM_PUB_PREFIX (default http://local\
host/pub)
    * Example: new stream "foo" will get pub URL: https://localhost/pub/foo
This commit is contained in:
Olof Hagsand 2018-10-15 22:08:38 +02:00
parent dcec834455
commit d5f4969780
15 changed files with 438 additions and 58 deletions

View file

@ -24,6 +24,11 @@
* See clicon_stream.[ch] for details
* 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.
* Set access/subscribe base URL with: CLICON_STREAM_URL_PREFIX (default https://localhost/streams).
* Example: new stream "foo" will get access URL: https://localhost/streams/foo
* Optional pub/sub support enabled by ./configure --enable-publish
* Set publish URL base with: CLICON_STREAM_PUB_PREFIX (default http://localhost/pub)
* Example: new stream "foo" will get pub URL: https://localhost/pub/foo
### API changes on existing features (you may need to change your code)
* Netconf hello capability updated to YANG 1.1 RFC7950 Sec 5.6.4

View file

@ -81,14 +81,20 @@ ce_find_bypid(struct client_entry *ce_list,
return NULL;
}
/*! Stream callback for netconf stream notification (RFC 5277)
* @param[in] h Clicon handle
* @param[in] event Event as XML
* @param[in] arg Extra argument provided in stream_cb_add
* @see stream_cb_add
*/
static int
ce_event_cb(clicon_handle h,
void *event,
cxobj *event,
void *arg)
{
struct client_entry *ce = (struct client_entry *)arg;
if (send_msg_notify_xml(ce->ce_s, (cxobj*)event) < 0){
if (send_msg_notify_xml(ce->ce_s, event) < 0){
if (errno == ECONNRESET || errno == EPIPE){
clicon_log(LOG_WARNING, "client %d reset", ce->ce_nr);
#if 0

View file

@ -91,6 +91,7 @@ backend_terminate(clicon_handle h)
yspec_free(yspec);
if ((x = clicon_conf_xml(h)) != NULL)
xml_free(x);
stream_publish_exit();
clixon_plugin_exit(h);
/* Delete all backend plugin RPC callbacks */
rpc_callback_delete_all();
@ -720,9 +721,9 @@ main(int argc,
return -1;
}
if (stream_register(h, "NETCONF", "default NETCONF event stream") < 0)
/* Publish stream on pubsub channels XXX conditional? */
if (stream_publish_init() < 0)
goto done;
if ((xmldb_plugin = clicon_xmldb_plugin(h)) == NULL){
clicon_log(LOG_ERR, "No xmldb plugin given (specify option CLICON_XMLDB_PLUGIN).\n");
goto done;

View file

@ -664,6 +664,7 @@ main(int argc,
clicon_err(OE_CFG, errno, "FCGX_Init");
goto done;
}
clicon_debug(1, "restconf_main: Opening FCGX socket: %s", sockpath);
if ((sock = FCGX_OpenSocket(sockpath, 10)) < 0){
clicon_err(OE_CFG, errno, "FCGX_OpenSocket");
goto done;

89
configure vendored
View file

@ -686,7 +686,6 @@ infodir
docdir
oldincludedir
includedir
runstatedir
localstatedir
sharedstatedir
sysconfdir
@ -711,6 +710,7 @@ ac_user_opts='
enable_option_checking
enable_debug
with_cligen
enable_publish
with_restconf
with_configfile
'
@ -763,7 +763,6 @@ datadir='${datarootdir}'
sysconfdir='${prefix}/etc'
sharedstatedir='${prefix}/com'
localstatedir='${prefix}/var'
runstatedir='${localstatedir}/run'
includedir='${prefix}/include'
oldincludedir='/usr/include'
docdir='${datarootdir}/doc/${PACKAGE}'
@ -1016,15 +1015,6 @@ do
| -silent | --silent | --silen | --sile | --sil)
silent=yes ;;
-runstatedir | --runstatedir | --runstatedi | --runstated \
| --runstate | --runstat | --runsta | --runst | --runs \
| --run | --ru | --r)
ac_prev=runstatedir ;;
-runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
| --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
| --run=* | --ru=* | --r=*)
runstatedir=$ac_optarg ;;
-sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
ac_prev=sbindir ;;
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
@ -1162,7 +1152,7 @@ fi
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
datadir sysconfdir sharedstatedir localstatedir includedir \
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
libdir localedir mandir runstatedir
libdir localedir mandir
do
eval ac_val=\$$ac_var
# Remove trailing slashes.
@ -1315,7 +1305,6 @@ Fine tuning of the installation directories:
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
--runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
--libdir=DIR object code libraries [EPREFIX/lib]
--includedir=DIR C header files [PREFIX/include]
--oldincludedir=DIR C header files for non-gcc [/usr/include]
@ -1349,6 +1338,8 @@ Optional Features:
--disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
--enable-FEATURE[=ARG] include FEATURE [ARG=yes]
--enable-debug Build with debug symbols, default: no
--enable-publish Enable publish of notification streams using SSE and
curl
Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
@ -2195,6 +2186,7 @@ _ACEOF
# AC_SUBST(var) makes @var@ appear in makefiles.
# clixon versions spread to Makefile's (.so files) and variable in build.c
@ -3687,6 +3679,77 @@ if test "${with_cligen}"; then
test -d "$with_cligen" && CLIGEN_PREFIX="$with_cligen"
fi
# Experimental: Curl publish notification stream to eg Nginx nchan.
# Check whether --enable-publish was given.
if test "${enable_publish+set}" = set; then :
enableval=$enable_publish;
if test "$enableval" = no; then
ac_enable_publish=no
else
ac_enable_publish=yes
fi
else
ac_enable_publish=no
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: publish is $ac_enable_publish" >&5
$as_echo "publish is $ac_enable_publish" >&6; }
if test "$ac_enable_publish" = "yes"; then
# publish streams uses libcurl
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl_global_init in -lcurl" >&5
$as_echo_n "checking for curl_global_init in -lcurl... " >&6; }
if ${ac_cv_lib_curl_curl_global_init+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_check_lib_save_LIBS=$LIBS
LIBS="-lcurl $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
#ifdef __cplusplus
extern "C"
#endif
char curl_global_init ();
int
main ()
{
return curl_global_init ();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
ac_cv_lib_curl_curl_global_init=yes
else
ac_cv_lib_curl_curl_global_init=no
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
LIBS=$ac_check_lib_save_LIBS
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_curl_curl_global_init" >&5
$as_echo "$ac_cv_lib_curl_curl_global_init" >&6; }
if test "x$ac_cv_lib_curl_curl_global_init" = xyes; then :
cat >>confdefs.h <<_ACEOF
#define HAVE_LIBCURL 1
_ACEOF
LIBS="-lcurl $LIBS"
else
as_fn_error $? "libcurl missing" "$LINENO" 5
fi
$as_echo "#define CLIXON_PUBLISH_STREAMS 1" >>confdefs.h
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
$as_echo_n "checking for grep that handles long lines and -e... " >&6; }

View file

@ -63,6 +63,7 @@ AC_DEFINE_UNQUOTED(CLIXON_VERSION_MINOR, $CLIXON_VERSION_MINOR, [Clixon minor re
AC_DEFINE_UNQUOTED(CLIXON_VERSION_PATCH, $CLIXON_VERSION_PATCH, [Clixon path version])
# AC_SUBST(var) makes @var@ appear in makefiles.
# clixon versions spread to Makefile's (.so files) and variable in build.c
AC_SUBST(CLIXON_VERSION)
AC_SUBST(CLIXON_VERSION_STRING)
@ -144,6 +145,23 @@ if test "${with_cligen}"; then
test -d "$with_cligen" && CLIGEN_PREFIX="$with_cligen"
fi
# Experimental: Curl publish notification stream to eg Nginx nchan.
AC_ARG_ENABLE(publish, AS_HELP_STRING([--enable-publish],[Enable publish of notification streams using SSE and curl]),[
if test "$enableval" = no; then
ac_enable_publish=no
else
ac_enable_publish=yes
fi
],
[ ac_enable_publish=no])
AC_MSG_RESULT(publish is $ac_enable_publish)
if test "$ac_enable_publish" = "yes"; then
# publish streams uses libcurl
AC_CHECK_LIB(curl, curl_global_init,, AC_MSG_ERROR([libcurl missing]))
AC_DEFINE(CLIXON_PUBLISH_STREAMS, 1, [Enable publish of notification streams using SSE and curl])
fi
AC_CHECK_HEADERS(cligen/cligen.h,, AC_MSG_ERROR(cligen missing. Try: git clone https://github.com/olofhagsand/cligen.git))
AC_CHECK_LIB(cligen, cligen_init,, AC_MSG_ERROR([CLIgen${CLIGEN_VERSION} missing. Try: git clone https://github.com/olofhagsand/cligen.git]))

View file

@ -125,7 +125,7 @@ text_db2file(struct text_handle *th,
char **filename)
{
int retval = -1;
cbuf *cb;
cbuf *cb = NULL;
char *dir;
if ((cb = cbuf_new()) == NULL){

View file

@ -59,7 +59,6 @@ NETCONF_PLUGIN = $(APPNAME)_netconf.so
RESTCONF_PLUGIN = $(APPNAME)_restconf.so
# Example docker image. PLEASE CHANGE THIS
IMAGE = olofhagsand/clixon_example

View file

@ -22,12 +22,38 @@ module example {
base if:interface-type;
}
/* Translation function example - See also example_cli */
list translate{
leaf value{
type string;
}
}
/* State data (not config) for the example application*/
container state {
config false;
description "state data for the example application (must be here for example get operation)";
leaf-list op {
type string;
}
}
/* Example notification as used in RFC 5277 and RFC 8040 */
notification event {
description "Example notification event.";
leaf event-class {
type string;
description "Event class identifier.";
}
container reportingEntity {
description "Event specific information.";
leaf card {
type string;
description "Line card identifier.";
}
}
leaf severity {
type string;
description "Event severity description.";
}
}
rpc client-rpc {
description "Example local client-side RPC that is processed by the
the netconf/restconf and not sent to the backend.

View file

@ -31,17 +31,14 @@
***** END LICENSE BLOCK *****
*
* IETF yang routing example
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <assert.h>
#include <sys/time.h>
/* clicon */
@ -54,7 +51,7 @@
#include <clixon/clixon_backend.h>
/* forward */
static int notification_timer_setup(clicon_handle h);
static int example_stream_timer_setup(clicon_handle h);
/*! This is called on validate (and commit). Check validity of candidate
*/
@ -93,33 +90,33 @@ transaction_commit(clicon_handle h,
/*! Routing example notifcation timer handler. Here is where the periodic action is
*/
static int
notification_timer(int fd,
example_stream_timer(int fd,
void *arg)
{
int retval = -1;
clicon_handle h = (clicon_handle)arg;
/* XXX Change to actual netconf notifications */
if (stream_notify(h, "NETCONF", "<event xmlns=\"http://example.com/event/1.0\"><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event>") < 0)
if (stream_notify(h, "EXAMPLE", "<event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event>") < 0)
goto done;
if (notification_timer_setup(h) < 0)
if (example_stream_timer_setup(h) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Set up routing notification timer
/*! Set up example stream notification timer
*/
static int
notification_timer_setup(clicon_handle h)
example_stream_timer_setup(clicon_handle h)
{
struct timeval t, t1;
gettimeofday(&t, NULL);
t1.tv_sec = 5; t1.tv_usec = 0;
timeradd(&t, &t1, &t);
return event_reg_timeout(t, notification_timer, h, "notification timer");
return event_reg_timeout(t, example_stream_timer, h, "example stream timer");
}
/*! IETF Routing fib-route rpc
@ -266,13 +263,19 @@ example_start(clicon_handle h,
return 0;
}
int
example_exit(clicon_handle h)
{
return 0;
}
clixon_plugin_api *clixon_plugin_init(clicon_handle h);
static clixon_plugin_api api = {
"example", /* name */
clixon_plugin_init, /* init - must be called clixon_plugin_init */
example_start, /* start */
NULL, /* exit */
example_exit, /* exit */
.ca_reset=example_reset, /* reset */
.ca_statedata=example_statedata, /* statedata */
.ca_trans_begin=NULL, /* trans begin */
@ -292,8 +295,21 @@ clixon_plugin_api *
clixon_plugin_init(clicon_handle h)
{
clicon_debug(1, "%s backend", __FUNCTION__);
if (notification_timer_setup(h) < 0)
/* Example stream initialization:
* 1) Register EXAMPLE stream
* 2) setup timer for notifications, so something happens on stream
* 3) setup stream callbacks for notification to push channel
*/
if (stream_register(h, "EXAMPLE", "Example event stream") < 0)
goto done;
/* assumes: CLIXON_PUBLISH_STREAMS, eg configure --enable-publish
*/
if (stream_publish(h, "EXAMPLE") < 0)
goto done;
if (example_stream_timer_setup(h) < 0)
goto done;
/* Register callback for routing rpc calls */
if (rpc_callback_register(h, fib_route,
NULL,
@ -310,6 +326,7 @@ clixon_plugin_init(clicon_handle h)
"empty"/* Xml tag when callback is made */
) < 0)
goto done;
/* Return plugin API */
return &api;
done:

View file

@ -6,6 +6,9 @@
/* Location for apps to find default config file */
#undef CLIXON_DEFAULT_CONFIG
/* Enable publish of notification streams using SSE and curl */
#undef CLIXON_PUBLISH_STREAMS
/* Clixon major release */
#undef CLIXON_VERSION_MAJOR

View file

@ -39,8 +39,13 @@
/*
* Types
*/
/* subscription callback */
typedef int (*stream_fn_t)(clicon_handle h, void *event, void *arg);
/* Subscription callback
* @param[in] h Clicon handle
* @param[in] event Event as XML
* @param[in] arg Extra argument provided in stream_cb_add
* @see stream_cb_add
*/
typedef int (*stream_fn_t)(clicon_handle h, cxobj *event, void *arg);
struct stream_subscription{
struct stream_subscription *ss_next;
@ -76,6 +81,11 @@ int stream_notify(clicon_handle h, char *stream, const char *event, ...) __attr
int stream_notify(clicon_handle h, char *stream, const char *event, ...);
#endif
/* Experimental publish streams using SSE */
int stream_publish(clicon_handle h, char *stream);
int stream_publish_init();
int stream_publish_exit();
/* Backward compatible macro for <1.8 */
#define backend_notify_xml(h, stream, level, x) stream_notify_xml(h, stream, x)
#define backend_notify(h, stream, level, event) stream_notify(h, stream, event)

View file

@ -44,6 +44,7 @@
#include <string.h>
#include <errno.h>
#include <inttypes.h>
#include <syslog.h>
#include <sys/time.h>
/* cligen */
@ -52,6 +53,7 @@
/* clicon */
#include "clixon_queue.h"
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_string.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
@ -146,6 +148,7 @@ stream_get_xml(clicon_handle h,
cbuf *cb)
{
event_stream_t *es = NULL;
char *url_prefix;
cprintf(cb, "<streams>");
for (es=clicon_stream(h); es; es=es->es_next){
@ -157,11 +160,9 @@ stream_get_xml(clicon_handle h,
if (access){
cprintf(cb, "<access>");
cprintf(cb, "<encoding>xml</encoding>");
/* Note /stream need to be in http proxy declaration
* XXX
*/
cprintf(cb, "<location>/stream/%s</location>", es->es_name);
url_prefix = clicon_option_str(h, "CLICON_STREAM_URL_PREFIX");
cprintf(cb, "<location>%s/%s</location>",
url_prefix, es->es_name);
cprintf(cb, "</access>");
}
cprintf(cb, "</stream>");
@ -377,3 +378,207 @@ stream_notify(clicon_handle h,
free(str);
return retval;
}
#ifdef CLIXON_PUBLISH_STREAMS
#include <curl/curl.h>
/*
* Types (curl)
*/
struct curlbuf{
size_t b_len;
char *b_buf;
};
/*
* For the asynchronous case. I think we must handle the case where of many of these
* come in before we can handle them in the upper-level polling routine.
* realloc. Therefore, we append new data to the userdata buffer.
*/
static size_t
curl_get_cb(void *ptr,
size_t size,
size_t nmemb,
void *userdata)
{
struct curlbuf *buf = (struct curlbuf *)userdata;
int len;
len = size*nmemb;
if ((buf->b_buf = realloc(buf->b_buf, buf->b_len+len+1)) == NULL)
return 0;
memcpy(buf->b_buf+buf->b_len, ptr, len);
buf->b_len += len;
buf->b_buf[buf->b_len] = '\0';
fprintf(stderr, "%s: %s\n", __FUNCTION__, buf->b_buf);
return len;
}
/*! Send a curl POST request
* @retval -1 fatal error
* @retval 0 expect set but did not expected return or other non-fatal error
* @retval 1 ok
* Note: curl_easy_perform blocks
* Note: New handle is created every time, the handle can be re-used for better TCP performance
* @see same function (url_post) in grideye_curl.c
*/
static int
url_post(char *url,
char *postfields,
char **getdata)
{
int retval = -1;
CURL *curl = NULL;
char *err = NULL;
struct curlbuf cb = {0, };
CURLcode errcode;
/* Try it with curl -X PUT -d '*/
clicon_debug(1, "%s: curl -X POST -d '%s' %s",
__FUNCTION__, postfields, url);
/* Set up curl for doing the communication with the controller */
if ((curl = curl_easy_init()) == NULL) {
clicon_debug(1, "curl_easy_init");
goto done;
}
if ((err = malloc(CURL_ERROR_SIZE)) == NULL) {
clicon_debug(1, "%s: malloc", __FUNCTION__);
goto done;
}
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_get_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &cb);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, err);
curl_easy_setopt(curl, CURLOPT_POST, 1);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(postfields));
if (debug)
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
if ((errcode = curl_easy_perform(curl)) != CURLE_OK){
clicon_debug(1, "%s: curl: %s(%d)", __FUNCTION__, err, errcode);
retval = 0;
goto done;
}
if (getdata && cb.b_buf){
*getdata = cb.b_buf;
cb.b_buf = NULL;
}
retval = 1;
done:
if (err)
free(err);
if (cb.b_buf)
free(cb.b_buf);
if (curl)
curl_easy_cleanup(curl); /* cleanup */
return retval;
}
/*! Stream callback for example stream notification
* Push via curl_post to publish stream event
* @param[in] h Clicon handle
* @param[in] event Event as XML
* @param[in] arg Extra argument provided in stream_cb_add
* @see stream_cb_add
*/
static int
stream_publish_cb(clicon_handle h,
cxobj *event,
void *arg)
{
int retval = -1;
cbuf *u = NULL; /* stream pub (push) url */
cbuf *d = NULL; /* (XML) data to push */
char *pub_prefix;
char *result = NULL;
char *stream = (char*)arg;
clicon_debug(1, "%s", __FUNCTION__);
/* Create pub url */
if ((u = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if ((pub_prefix = clicon_option_str(h, "CLICON_STREAM_PUB_PREFIX")) == NULL){
clicon_err(OE_CFG, ENOENT, "CLICON_STREAM_PUB_PREFIX not defined");
goto done;
}
cprintf(u, "%s/%s", pub_prefix, stream);
/* Create XML data as string */
if ((d = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (clicon_xml2cbuf(d, event, 0, 0) < 0)
goto done;
if (url_post(cbuf_get(u), /* url+stream */
cbuf_get(d), /* postfields */
&result) < 0) /* result as xml */
goto done;
if (result)
clicon_debug(1, "%s: %s", __FUNCTION__, result);
retval = 0;
done:
if (u)
cbuf_free(u);
if (d)
cbuf_free(d);
if (result)
free(result);
return retval;
}
#endif /* CLIXON_PUBLISH_STREAMS */
/*! Publish all streams on a pubsub channel, eg using SSE
*/
int
stream_publish(clicon_handle h,
char *stream)
{
#ifdef CLIXON_PUBLISH_STREAMS
int retval = -1;
if (stream_cb_add(h, stream, NULL, stream_publish_cb, (void*)stream) < 0)
goto done;
retval = 0;
done:
return retval;
#else
clicon_log(LOG_WARNING, "%s called but CLIXON_PUBLISH_STREAMS not enabled (enable with configure --enable-publish)", __FUNCTION__);
clicon_log_init("xpath", LOG_WARNING, CLICON_LOG_STDERR);
return 0;
#endif
}
int
stream_publish_init()
{
#ifdef CLIXON_PUBLISH_STREAMS
int retval = -1;
if (curl_global_init(CURL_GLOBAL_ALL) != 0){
clicon_err(OE_PLUGIN, errno, "curl_global_init");
goto done;
}
retval = 0;
done:
return retval;
#else
return 0;
#endif
}
int
stream_publish_exit()
{
#ifdef CLIXON_PUBLISH_STREAMS
curl_global_cleanup();
#endif
return 0;
}

View file

@ -1,6 +1,11 @@
#!/bin/bash
# Restconf basic functionality
# Assume http server setup, such as nginx described in apps/restconf/README.md
# Tests for event streams using notifications
# Assumptions:
# 1. http server setup, such as nginx described in apps/restconf/README.md
# especially SSE - ngchan setup
# 2. Example stream as Clixon example which needs registration, callback and
# notification generating code
APPNAME=example
# include err() and new() functions and creates $dir
. ./lib.sh
@ -8,6 +13,7 @@ cfg=$dir/conf.xml
fyang=$dir/restconf.yang
xml=$dir/xml.xml
# <CLICON_YANG_MODULE_MAIN>example</CLICON_YANG_MODULE_MAIN>
cat <<EOF > $cfg
<config>
@ -62,7 +68,7 @@ cat <<EOF > $fyang
}
container state {
config false;
description "state data for example application";
description "state data for the example application (must be here for example get operation)";
leaf-list op {
type string;
}
@ -92,28 +98,30 @@ sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c
sleep 1
new "netconf event stream discovery RFC5277 Sec 3.2.5"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><get><filter type="xpath" select="netconf/streams" xmlns="urn:ietf:params:xml:ns:netmod:notification"/></get></rpc>]]>]]>' '<rpc-reply><data><netconf><streams><stream><name>NETCONF</name><description>default NETCONF event stream</description><replay-support>false</replay-support></stream></streams></netconf></data></rpc-reply>]]>]]>'
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><get><filter type="xpath" select="netconf/streams" xmlns="urn:ietf:params:xml:ns:netmod:notification"/></get></rpc>]]>]]>' '<rpc-reply><data><netconf><streams><stream><name>EXAMPLE</name><description>Example event stream</description><replay-support>false</replay-support></stream></streams></netconf></data></rpc-reply>]]>]]>'
new "netconf event stream discovery RFC8040 Sec 6.2"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><get><filter type="xpath" select="restconf-state/streams" xmlns="urn:ietf:params:xml:ns:netmod:notification"/></get></rpc>]]>]]>' '<rpc-reply><data><restconf-state><streams><stream><name>NETCONF</name><description>default NETCONF event stream</description><replay-support>false</replay-support><access><encoding>xml</encoding><location>/stream/NETCONF</location></access></stream></streams></restconf-state></data></rpc-reply>]]>]]>'
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><get><filter type="xpath" select="restconf-state/streams" xmlns="urn:ietf:params:xml:ns:netmod:notification"/></get></rpc>]]>]]>' '<rpc-reply><data><restconf-state><streams><stream><name>EXAMPLE</name><description>Example event stream</description><replay-support>false</replay-support><access><encoding>xml</encoding><location>https://localhost/streams/EXAMPLE</location></access></stream></streams></restconf-state></data></rpc-reply>]]>]]>'
new "restconf event stream discovery RFC8040 Sec 6.2"
expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams" 0 '{"streams": {"stream": \[{"name": "NETCONF","description": "default NETCONF event stream","replay-support": false,"access": \[{"encoding": "xml","location": "/stream/NETCONF"}\]}\]}'
expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams" 0 '{"streams": {"stream": \[{"name": "EXAMPLE","description": "Example event stream","replay-support": false,"access": \[{"encoding": "xml","location": "https://localhost/streams/EXAMPLE"}\]}\]}'
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=NETCONF/access=xml/location" 0 '{"location": "/stream/NETCONF"}'
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"}'
new "netconf NETCONF subscription"
expectwait "$clixon_netconf -qf $cfg -y $fyang" '<rpc><create-subscription><stream>NETCONF</stream></create-subscription></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]><notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>20' 5
if false; then
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
new "netconf NETCONF subscription with simple filter"
expectwait "$clixon_netconf -qf $cfg -y $fyang" "<rpc><create-subscription><stream>NETCONF</stream><filter type=\"xpath\" select=\"event\"/></create-subscription></rpc>]]>]]>" '^<rpc-reply><ok/></rpc-reply>]]>]]><notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>20' 5
new "netconf EXAMPLE subscription with simple filter"
expectwait "$clixon_netconf -qf $cfg -y $fyang" "<rpc><create-subscription><stream>EXAMPLE</stream><filter type=\"xpath\" select=\"event\"/></create-subscription></rpc>]]>]]>" '^<rpc-reply><ok/></rpc-reply>]]>]]><notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>20' 5
new "netconf NETCONF subscription with filter classifier"
expectwait "$clixon_netconf -qf $cfg -y $fyang" "<rpc><create-subscription><stream>NETCONF</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
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
fi
#new "restconf monitor event stream RFC8040 Sec 6.3"
#XXX expectfn "curl -s -X GET http://localhost/stream/NETCONF" 0 ''
new "restconf monitor event stream RFC8040 Sec 6.3"
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>'
new "Kill restconf daemon"
sudo pkill -u www-data clixon_restconf

View file

@ -390,5 +390,23 @@ module clixon-config {
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";
}
}
}