stream replay and subscription update
This commit is contained in:
parent
25c761202e
commit
fb0d0606e3
5 changed files with 285 additions and 138 deletions
|
|
@ -85,6 +85,7 @@
|
||||||
```
|
```
|
||||||
|
|
||||||
### Minor changes
|
### Minor changes
|
||||||
|
* Allow new lines in CLI prompts
|
||||||
* uri_percent_encode() and xml_chardata_encode() changed to use stdarg parameters
|
* uri_percent_encode() and xml_chardata_encode() changed to use stdarg parameters
|
||||||
* Added CLIXON_DEFAULT_CONFIG=/usr/local/etc/clixon.xml as option and in example (so you dont need to provide -f command-line option).
|
* Added CLIXON_DEFAULT_CONFIG=/usr/local/etc/clixon.xml as option and in example (so you dont need to provide -f command-line option).
|
||||||
* Yang 1.1 action syntax added (but function is not supported)
|
* Yang 1.1 action syntax added (but function is not supported)
|
||||||
|
|
|
||||||
|
|
@ -84,8 +84,8 @@ ce_find_bypid(struct client_entry *ce_list,
|
||||||
/*! Stream callback for netconf stream notification (RFC 5277)
|
/*! Stream callback for netconf stream notification (RFC 5277)
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] event Event as XML
|
* @param[in] event Event as XML
|
||||||
* @param[in] arg Extra argument provided in stream_cb_add
|
* @param[in] arg Extra argument provided in stream_ss_add
|
||||||
* @see stream_cb_add
|
* @see stream_ss_add
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
ce_event_cb(clicon_handle h,
|
ce_event_cb(clicon_handle h,
|
||||||
|
|
@ -116,7 +116,7 @@ ce_event_cb(clicon_handle h,
|
||||||
* Close down everything wrt clients (eg sockets, subscriptions)
|
* Close down everything wrt clients (eg sockets, subscriptions)
|
||||||
* Finally actually remove client struct in handle
|
* Finally actually remove client struct in handle
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] ce Client hadnle
|
* @param[in] ce Client handle
|
||||||
* @see backend_client_delete for actual deallocation of client entry struct
|
* @see backend_client_delete for actual deallocation of client entry struct
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
|
|
@ -137,7 +137,7 @@ backend_client_rm(clicon_handle h,
|
||||||
ce->ce_s = 0;
|
ce->ce_s = 0;
|
||||||
}
|
}
|
||||||
/* for all streams */
|
/* for all streams */
|
||||||
stream_cb_delete(h, NULL, ce_event_cb, (void*)ce);
|
stream_ss_delete_all(h, ce_event_cb, (void*)ce);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ce_prev = &c->ce_next;
|
ce_prev = &c->ce_next;
|
||||||
|
|
@ -807,7 +807,6 @@ from_client_delete_config(clicon_handle h,
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*! Internal message: Create subscription for notifications see RFC 5277
|
/*! Internal message: Create subscription for notifications see RFC 5277
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] xe Netconf request xml tree
|
* @param[in] xe Netconf request xml tree
|
||||||
|
|
@ -840,7 +839,7 @@ from_client_create_subscription(clicon_handle h,
|
||||||
char *selector = NULL;
|
char *selector = NULL;
|
||||||
struct timeval start;
|
struct timeval start;
|
||||||
struct timeval stop;
|
struct timeval stop;
|
||||||
|
|
||||||
if ((x = xpath_first(xe, "//stream")) != NULL)
|
if ((x = xpath_first(xe, "//stream")) != NULL)
|
||||||
stream = xml_find_value(x, "body");
|
stream = xml_find_value(x, "body");
|
||||||
if ((x = xpath_first(xe, "//stopTime")) != NULL){
|
if ((x = xpath_first(xe, "//stopTime")) != NULL){
|
||||||
|
|
@ -874,13 +873,20 @@ from_client_create_subscription(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
if (stream_cb_add(h, stream, selector, stoptime?&stop:NULL,
|
if (stream_ss_add(h, stream, selector,
|
||||||
|
starttime?&start:NULL, stoptime?&stop:NULL,
|
||||||
ce_event_cb, (void*)ce) < 0)
|
ce_event_cb, (void*)ce) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
/* Replay of this stream to specific subscription according to start and stop*/
|
/* Replay of this stream to specific subscription according to start and
|
||||||
if (starttime) /* XXX No this must be done _after_ this RPC completes */
|
* stop (if present).
|
||||||
if (stream_replay(h, stream, &start, stoptime?&stop:NULL) < 0)
|
* RFC 5277: If <startTime> is not present, this is not a replay
|
||||||
|
* subscription.
|
||||||
|
* Schedule the replay to occur right after this RPC completes, eg "now"
|
||||||
|
*/
|
||||||
|
if (starttime){
|
||||||
|
if (stream_replay_trigger(h, stream, ce_event_cb, (void*)ce) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
}
|
||||||
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
|
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
|
||||||
ok:
|
ok:
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
@ -1274,7 +1280,7 @@ from_client_msg(clicon_handle h,
|
||||||
}
|
}
|
||||||
else if (strcmp(name, "close-session") == 0){
|
else if (strcmp(name, "close-session") == 0){
|
||||||
xmldb_unlock_all(h, pid);
|
xmldb_unlock_all(h, pid);
|
||||||
stream_cb_delete(h, NULL, ce_event_cb, (void*)ce);
|
stream_ss_delete_all(h, ce_event_cb, (void*)ce);
|
||||||
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
|
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
|
||||||
}
|
}
|
||||||
else if (strcmp(name, "kill-session") == 0){
|
else if (strcmp(name, "kill-session") == 0){
|
||||||
|
|
|
||||||
|
|
@ -42,15 +42,16 @@
|
||||||
/* Subscription callback
|
/* Subscription callback
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] event Event as XML
|
* @param[in] event Event as XML
|
||||||
* @param[in] arg Extra argument provided in stream_cb_add
|
* @param[in] arg Extra argument provided in stream_ss_add
|
||||||
* @see stream_cb_add
|
* @see stream_ss_add
|
||||||
*/
|
*/
|
||||||
typedef int (*stream_fn_t)(clicon_handle h, cxobj *event, void *arg);
|
typedef int (*stream_fn_t)(clicon_handle h, cxobj *event, void *arg);
|
||||||
|
|
||||||
struct stream_subscription{
|
struct stream_subscription{
|
||||||
struct stream_subscription *ss_next;
|
qelem_t ss_q; /* queue header */
|
||||||
char *ss_stream; /* Name of associated stream */
|
char *ss_stream; /* Name of associated stream */
|
||||||
char *ss_xpath; /* Filter selector as xpath */
|
char *ss_xpath; /* Filter selector as xpath */
|
||||||
|
struct timeval ss_starttime; /* Replay starttime */
|
||||||
struct timeval ss_stoptime; /* Replay stoptime */
|
struct timeval ss_stoptime; /* Replay stoptime */
|
||||||
stream_fn_t ss_fn; /* Callback when event occurs */
|
stream_fn_t ss_fn; /* Callback when event occurs */
|
||||||
void *ss_arg; /* Callback argument */
|
void *ss_arg; /* Callback argument */
|
||||||
|
|
@ -82,19 +83,26 @@ event_stream_t *stream_find(clicon_handle h, const char *name);
|
||||||
int stream_register(clicon_handle h, const char *name, const char *description, int replay_enabled);
|
int stream_register(clicon_handle h, const char *name, const char *description, int replay_enabled);
|
||||||
int stream_delete_all(event_stream_t *es);
|
int stream_delete_all(event_stream_t *es);
|
||||||
int stream_get_xml(clicon_handle h, int access, cbuf *cb);
|
int stream_get_xml(clicon_handle h, int access, cbuf *cb);
|
||||||
struct stream_subscription *stream_cb_add(clicon_handle h, char *stream,
|
int stream_timer_setup(int fd, void *arg);
|
||||||
char *xpath, struct timeval *stop, stream_fn_t fn, void *arg);
|
/* Subscriptions */
|
||||||
int stream_cb_delete(clicon_handle h, char *stream, stream_fn_t fn, void *arg);
|
struct stream_subscription *stream_ss_add(clicon_handle h, char *stream,
|
||||||
int stream_notify_xml(clicon_handle h, event_stream_t *es, cxobj *xevent);
|
char *xpath, struct timeval *start, struct timeval *stop,
|
||||||
|
stream_fn_t fn, void *arg);
|
||||||
|
int stream_ss_rm(event_stream_t *es, struct stream_subscription *ss);
|
||||||
|
struct stream_subscription *stream_ss_find(event_stream_t *es,
|
||||||
|
stream_fn_t fn, void *arg);
|
||||||
|
int stream_ss_delete_all(clicon_handle h, stream_fn_t fn, void *arg);
|
||||||
|
|
||||||
|
int stream_notify_xml(clicon_handle h, event_stream_t *es, struct timeval *tv, cxobj *xevent);
|
||||||
#if defined(__GNUC__) && __GNUC__ >= 3
|
#if defined(__GNUC__) && __GNUC__ >= 3
|
||||||
int stream_notify(clicon_handle h, char *stream, const char *event, ...) __attribute__ ((format (printf, 3, 4)));
|
int stream_notify(clicon_handle h, char *stream, const char *event, ...) __attribute__ ((format (printf, 3, 4)));
|
||||||
#else
|
#else
|
||||||
int stream_notify(clicon_handle h, char *stream, const char *event, ...);
|
int stream_notify(clicon_handle h, char *stream, const char *event, ...);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Replay */
|
||||||
int stream_replay(clicon_handle h, char *stream, struct timeval *start, struct timeval *stop);
|
|
||||||
int stream_replay_add(event_stream_t *es, struct timeval *tv, cxobj *xv);
|
int stream_replay_add(event_stream_t *es, struct timeval *tv, cxobj *xv);
|
||||||
|
int stream_replay_trigger(clicon_handle h, char *stream, stream_fn_t fn, void *arg);
|
||||||
|
|
||||||
/* Experimental publish streams using SSE. CLIXON_PUBLISH_STREAMS should be set */
|
/* Experimental publish streams using SSE. CLIXON_PUBLISH_STREAMS should be set */
|
||||||
int stream_publish(clicon_handle h, char *stream);
|
int stream_publish(clicon_handle h, char *stream);
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,17 @@
|
||||||
* Event notification streams according to RFC5277
|
* Event notification streams according to RFC5277
|
||||||
* The stream implementation has three parts:
|
* The stream implementation has three parts:
|
||||||
* 1) Base stream handling: stream_find/register/delete_all/get_xml
|
* 1) Base stream handling: stream_find/register/delete_all/get_xml
|
||||||
* 2) Callback and notification handling (stream_cb_add/delete/timeout, stream_notify, etc
|
* 2) Stream subscription handling (stream_ss_add/delete/timeout, stream_notify, etc
|
||||||
* 3) Stream replay: stream_replay/_add
|
* 3) Stream replay: stream_replay/_add
|
||||||
* 4) nginx/nchan publish code (use --enable-publish config option)
|
* 4) nginx/nchan publish code (use --enable-publish config option)
|
||||||
|
*
|
||||||
|
* +---------------+ * +---------------+ * +---------------+
|
||||||
|
* | clicon_handle |--------->| event_stream |--------->| subscription |
|
||||||
|
* +---------------+ +---------------+ +---------------+
|
||||||
|
* \ * +---------------+
|
||||||
|
* +-->| replay |
|
||||||
|
* +---------------+
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifdef HAVE_CONFIG_H
|
#ifdef HAVE_CONFIG_H
|
||||||
|
|
@ -58,6 +66,7 @@
|
||||||
#include "clixon_queue.h"
|
#include "clixon_queue.h"
|
||||||
#include "clixon_err.h"
|
#include "clixon_err.h"
|
||||||
#include "clixon_log.h"
|
#include "clixon_log.h"
|
||||||
|
#include "clixon_event.h"
|
||||||
#include "clixon_string.h"
|
#include "clixon_string.h"
|
||||||
#include "clixon_hash.h"
|
#include "clixon_hash.h"
|
||||||
#include "clixon_handle.h"
|
#include "clixon_handle.h"
|
||||||
|
|
@ -68,6 +77,9 @@
|
||||||
#include "clixon_xpath.h"
|
#include "clixon_xpath.h"
|
||||||
#include "clixon_stream.h"
|
#include "clixon_stream.h"
|
||||||
|
|
||||||
|
/* Go through and timeout subscription timers [s] */
|
||||||
|
#define STREAM_TIMER_TIMEOUT_S 5
|
||||||
|
|
||||||
/*! Find an event notification stream given name
|
/*! Find an event notification stream given name
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] name Name of stream
|
* @param[in] name Name of stream
|
||||||
|
|
@ -129,6 +141,7 @@ stream_delete_all(event_stream_t *es)
|
||||||
{
|
{
|
||||||
event_stream_t *e_next;
|
event_stream_t *e_next;
|
||||||
struct stream_replay *r;
|
struct stream_replay *r;
|
||||||
|
struct stream_subscription *ss;
|
||||||
|
|
||||||
while (es){
|
while (es){
|
||||||
e_next = es->es_next;
|
e_next = es->es_next;
|
||||||
|
|
@ -136,6 +149,8 @@ stream_delete_all(event_stream_t *es)
|
||||||
free(es->es_name);
|
free(es->es_name);
|
||||||
if (es->es_description)
|
if (es->es_description)
|
||||||
free(es->es_description);
|
free(es->es_description);
|
||||||
|
while ((ss = es->es_subscription) != NULL)
|
||||||
|
stream_ss_rm(es, ss);
|
||||||
while ((r = es->es_replay) != NULL){
|
while ((r = es->es_replay) != NULL){
|
||||||
if (r->r_xml)
|
if (r->r_xml)
|
||||||
xml_free(r->r_xml);
|
xml_free(r->r_xml);
|
||||||
|
|
@ -186,6 +201,54 @@ stream_get_xml(clicon_handle h,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! Check all stream subscription stop timers, set up new timer
|
||||||
|
* @param[in] fd No-op
|
||||||
|
* @param[in] arg Clicon handle
|
||||||
|
* @note format is given by event_reg_timeout callback function (fd not needed)
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
stream_timer_setup(int fd,
|
||||||
|
void *arg)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
clicon_handle h = (clicon_handle)arg;
|
||||||
|
struct timeval now;
|
||||||
|
struct timeval t;
|
||||||
|
struct timeval t1 = {STREAM_TIMER_TIMEOUT_S, 0};
|
||||||
|
event_stream_t *es;
|
||||||
|
struct stream_subscription *ss;
|
||||||
|
struct stream_subscription *ss1;
|
||||||
|
|
||||||
|
/* Go thru callbacks and see if any have timed out, if so remove them
|
||||||
|
* Could also be done by a separate timer.
|
||||||
|
*/
|
||||||
|
gettimeofday(&now, NULL);
|
||||||
|
/* for all event streams, remove subscription if past stop time */
|
||||||
|
for (es=clicon_stream(h); es; es=es->es_next){
|
||||||
|
if ((ss = es->es_subscription) != NULL)
|
||||||
|
do {
|
||||||
|
if (timerisset(&ss->ss_stoptime) && timercmp(&ss->ss_stoptime, &now, <)){
|
||||||
|
ss1 = NEXTQ(struct stream_subscription *, ss);
|
||||||
|
if (stream_ss_rm(es, ss) < 0)
|
||||||
|
goto done;
|
||||||
|
ss = ss1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ss = NEXTQ(struct stream_subscription *, ss);
|
||||||
|
} while (ss && ss != es->es_subscription);
|
||||||
|
}
|
||||||
|
/* Initiate new timer */
|
||||||
|
timeradd(&now, &t1, &t);
|
||||||
|
if (event_reg_timeout(t,
|
||||||
|
stream_timer_setup, /* this function */
|
||||||
|
h, /* clicon handle */
|
||||||
|
"stream timer setup") < 0)
|
||||||
|
goto done;
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef NYI
|
#ifdef NYI
|
||||||
/*! Delete single notification event stream
|
/*! Delete single notification event stream
|
||||||
* XXX notused
|
* XXX notused
|
||||||
|
|
@ -197,21 +260,22 @@ stream_del()
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/*! Add an event notification callback to a stream given a callback function
|
/*! Add an event notification callback to a stream given a callback function
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] stream Name of stream
|
* @param[in] stream Name of stream
|
||||||
* @param[in] xpath Filter selector - xpath
|
* @param[in] xpath Filter selector - xpath
|
||||||
* @param[in] stoptime If set,
|
* @param[in] startime If set, Make a replay
|
||||||
* @param[in] fn Callback when event occurs
|
* @param[in] stoptime If set, dont continue past this time
|
||||||
* @param[in] arg Argument to use with callback. Also handle when deleting
|
* @param[in] fn Callback when event occurs
|
||||||
* @retval 0 OK
|
* @param[in] arg Argument to use with callback. Also handle when deleting
|
||||||
* @retval -1 Error, ie no such stream
|
* @retval 0 OK
|
||||||
|
* @retval -1 Error, ie no such stream
|
||||||
*/
|
*/
|
||||||
struct stream_subscription *
|
struct stream_subscription *
|
||||||
stream_cb_add(clicon_handle h,
|
stream_ss_add(clicon_handle h,
|
||||||
char *stream,
|
char *stream,
|
||||||
char *xpath,
|
char *xpath,
|
||||||
|
struct timeval *starttime,
|
||||||
struct timeval *stoptime,
|
struct timeval *stoptime,
|
||||||
stream_fn_t fn,
|
stream_fn_t fn,
|
||||||
void *arg)
|
void *arg)
|
||||||
|
|
@ -235,14 +299,15 @@ stream_cb_add(clicon_handle h,
|
||||||
}
|
}
|
||||||
if (stoptime)
|
if (stoptime)
|
||||||
ss->ss_stoptime = *stoptime;
|
ss->ss_stoptime = *stoptime;
|
||||||
|
if (starttime)
|
||||||
|
ss->ss_starttime = *starttime;
|
||||||
if (xpath && (ss->ss_xpath = strdup(xpath)) == NULL){
|
if (xpath && (ss->ss_xpath = strdup(xpath)) == NULL){
|
||||||
clicon_err(OE_CFG, errno, "strdup");
|
clicon_err(OE_CFG, errno, "strdup");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
ss->ss_fn = fn;
|
ss->ss_fn = fn;
|
||||||
ss->ss_arg = arg;
|
ss->ss_arg = arg;
|
||||||
ss->ss_next = es->es_subscription;
|
ADDQ(ss, es->es_subscription);
|
||||||
es->es_subscription = ss;
|
|
||||||
return ss;
|
return ss;
|
||||||
done:
|
done:
|
||||||
if (ss)
|
if (ss)
|
||||||
|
|
@ -250,7 +315,7 @@ stream_cb_add(clicon_handle h,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Delete event notification callback to a stream given a callback and arg
|
/*! Delete event stream subscription to a stream given a callback and arg
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] stream Name of stream or NULL for all streams
|
* @param[in] stream Name of stream or NULL for all streams
|
||||||
* @param[in] fn Callback when event occurs
|
* @param[in] fn Callback when event occurs
|
||||||
|
|
@ -258,77 +323,61 @@ stream_cb_add(clicon_handle h,
|
||||||
* @retval 0 OK
|
* @retval 0 OK
|
||||||
* @retval -1 Error
|
* @retval -1 Error
|
||||||
*/
|
*/
|
||||||
static int
|
int
|
||||||
stream_cb_rm(event_stream_t *es,
|
stream_ss_rm(event_stream_t *es,
|
||||||
struct stream_subscription *ssrm)
|
struct stream_subscription *ss)
|
||||||
{
|
{
|
||||||
struct stream_subscription **ss_prev;
|
DELQ(ss, es->es_subscription, struct stream_subscription *);
|
||||||
struct stream_subscription *ss;
|
if (ss->ss_stream)
|
||||||
struct stream_subscription *ss_next;
|
free(ss->ss_stream);
|
||||||
|
if (ss->ss_xpath)
|
||||||
ss_prev = &es->es_subscription;
|
free(ss->ss_xpath);
|
||||||
for (ss = *ss_prev; ss; ss = ss_next){
|
free(ss);
|
||||||
ss_next = ss->ss_next;
|
|
||||||
if (ss == ssrm){
|
|
||||||
*ss_prev = ss->ss_next;
|
|
||||||
if (ss->ss_stream)
|
|
||||||
free(ss->ss_stream);
|
|
||||||
if (ss->ss_xpath)
|
|
||||||
free(ss->ss_xpath);
|
|
||||||
free(ss);
|
|
||||||
continue;
|
|
||||||
// break; if more > 1
|
|
||||||
}
|
|
||||||
ss_prev = &ss->ss_next;
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Remove stream subscription identified with fn and arg
|
/*! Find stream callback given callback function and its (unique) argument
|
||||||
|
* @param[in] es Pointer to event stream
|
||||||
|
* @param[in] fn Stream callback
|
||||||
|
* @param[in] arg Argument - typically unique client handle
|
||||||
|
* @retval ss Event stream subscription structure
|
||||||
|
* @retval NULL Not found
|
||||||
|
*/
|
||||||
|
struct stream_subscription *
|
||||||
|
stream_ss_find(event_stream_t *es,
|
||||||
|
stream_fn_t fn,
|
||||||
|
void *arg)
|
||||||
|
{
|
||||||
|
struct stream_subscription *ss;
|
||||||
|
|
||||||
|
if ((ss = es->es_subscription) != NULL)
|
||||||
|
do {
|
||||||
|
if (fn == ss->ss_fn && arg == ss->ss_arg)
|
||||||
|
return ss;
|
||||||
|
ss = NEXTQ(struct stream_subscription *, ss);
|
||||||
|
} while (ss && ss != es->es_subscription);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Remove stream subscription identified with fn and arg in all streams
|
||||||
|
* @param[in] h Clicon handle
|
||||||
|
* @param[in] stream Name of stream or NULL for all streams
|
||||||
|
* @param[in] fn Stream callback
|
||||||
|
* @param[in] arg Argument - typically unique client handle
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
stream_cb_delete(clicon_handle h,
|
stream_ss_delete_all(clicon_handle h,
|
||||||
char *stream,
|
stream_fn_t fn,
|
||||||
stream_fn_t fn,
|
void *arg)
|
||||||
void *arg)
|
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
event_stream_t *es;
|
event_stream_t *es;
|
||||||
struct stream_subscription *ss;
|
struct stream_subscription *ss;
|
||||||
|
|
||||||
for (es=clicon_stream(h); es; es=es->es_next){
|
|
||||||
if (stream && strcmp(stream, es->es_name)!=0)
|
|
||||||
continue;
|
|
||||||
for (ss = es->es_subscription; ss; ss = ss->ss_next){
|
|
||||||
if (fn == ss->ss_fn && arg == ss->ss_arg){
|
|
||||||
if (stream_cb_rm(es, ss) < 0)
|
|
||||||
goto done;
|
|
||||||
break; /* scb_rm removes element, must break - but only one match */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
retval = 0;
|
|
||||||
done:
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*! Check if any stream subscriptions have timed out, if so remove them
|
for (es=clicon_stream(h); es; es=es->es_next)
|
||||||
*/
|
if ((ss = stream_ss_find(es, fn, arg)) != NULL)
|
||||||
int
|
if (stream_ss_rm(es, ss) < 0)
|
||||||
stream_cb_timeout(event_stream_t *es,
|
|
||||||
struct timeval *tv)
|
|
||||||
{
|
|
||||||
int retval = -1;
|
|
||||||
struct stream_subscription *ss;
|
|
||||||
|
|
||||||
again: /* If we remove must start again */
|
|
||||||
for (ss = es->es_subscription; ss; ss = ss->ss_next){
|
|
||||||
if (timerisset(&ss->ss_stoptime) && timercmp(&ss->ss_stoptime, tv, <)){
|
|
||||||
if (stream_cb_rm(es, ss) < 0)
|
|
||||||
goto done;
|
goto done;
|
||||||
goto again;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
return retval;
|
return retval;
|
||||||
|
|
@ -337,29 +386,37 @@ stream_cb_timeout(event_stream_t *es,
|
||||||
/*! Stream notify event and distribute to all registered callbacks
|
/*! Stream notify event and distribute to all registered callbacks
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] stream Name of event stream. CLICON is predefined as LOG stream
|
* @param[in] stream Name of event stream. CLICON is predefined as LOG stream
|
||||||
|
* @param[in] tv Timestamp. Dont notify if subscription has stoptime<tv
|
||||||
* @param[in] event Notification as xml tree
|
* @param[in] event Notification as xml tree
|
||||||
* @retval 0 OK
|
* @retval 0 OK
|
||||||
* @retval -1 Error with clicon_err called
|
* @retval -1 Error with clicon_err called
|
||||||
* @see stream_notify
|
* @see stream_notify
|
||||||
|
* @see stream_ss_timeout where subscriptions are removed if stoptime<now
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
stream_notify_xml(clicon_handle h,
|
stream_notify_xml(clicon_handle h,
|
||||||
event_stream_t *es,
|
event_stream_t *es,
|
||||||
|
struct timeval *tv,
|
||||||
cxobj *xevent)
|
cxobj *xevent)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
struct stream_subscription *ss;
|
struct stream_subscription *ss;
|
||||||
|
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
/* Go thru all global (handle) subscriptions and find matches */
|
/* Go thru all subscriptions and find matches */
|
||||||
for (ss = es->es_subscription; ss; ss = ss->ss_next){
|
if ((ss = es->es_subscription) != NULL)
|
||||||
/* Check if this is of interest, ie not larger than stoptime? */
|
do {
|
||||||
if (ss->ss_xpath == NULL ||
|
if (timerisset(&ss->ss_stoptime) && /* stoptime has passed */
|
||||||
strlen(ss->ss_xpath)==0 ||
|
timercmp(&ss->ss_stoptime, tv, <))
|
||||||
xpath_first(xevent, "%s", ss->ss_xpath) != NULL)
|
;
|
||||||
if ((*ss->ss_fn)(h, xevent, ss->ss_arg) < 0)
|
else /* xpath match */
|
||||||
goto done;
|
if (ss->ss_xpath == NULL ||
|
||||||
}
|
strlen(ss->ss_xpath)==0 ||
|
||||||
|
xpath_first(xevent, "%s", ss->ss_xpath) != NULL)
|
||||||
|
if ((*ss->ss_fn)(h, xevent, ss->ss_arg) < 0)
|
||||||
|
goto done;
|
||||||
|
ss = NEXTQ(struct stream_subscription *, ss);
|
||||||
|
} while (ss && ss != es->es_subscription);
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
return retval;
|
return retval;
|
||||||
|
|
@ -411,18 +468,11 @@ stream_notify(clicon_handle h,
|
||||||
clicon_err(OE_YANG, 0, "No yang spec");
|
clicon_err(OE_YANG, 0, "No yang spec");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
gettimeofday(&tv, NULL);
|
|
||||||
#if 1
|
|
||||||
/* Go thru callbacks and see if any have timed out, if so remove them
|
|
||||||
* Could also be done by a separate timer.
|
|
||||||
*/
|
|
||||||
if (stream_cb_timeout(es, &tv) < 0)
|
|
||||||
goto done;
|
|
||||||
#endif
|
|
||||||
if ((cb = cbuf_new()) == NULL){
|
if ((cb = cbuf_new()) == NULL){
|
||||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
gettimeofday(&tv, NULL);
|
||||||
if (time2str(tv, timestr, sizeof(timestr)) < 0){
|
if (time2str(tv, timestr, sizeof(timestr)) < 0){
|
||||||
clicon_err(OE_UNIX, errno, "time2str");
|
clicon_err(OE_UNIX, errno, "time2str");
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -432,7 +482,7 @@ stream_notify(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
if (xml_rootchild(xev, 0, &xev) < 0)
|
if (xml_rootchild(xev, 0, &xev) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (stream_notify_xml(h, es, xev) < 0)
|
if (stream_notify_xml(h, es, &tv, xev) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (es->es_replay_enabled){
|
if (es->es_replay_enabled){
|
||||||
if (stream_replay_add(es, &tv, xev) < 0)
|
if (stream_replay_add(es, &tv, xev) < 0)
|
||||||
|
|
@ -451,8 +501,8 @@ stream_notify(clicon_handle h,
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! Replay a stream by sending notification messages
|
||||||
/*! Replay a stream
|
* @see RFC5277 Sec 2.1.1:
|
||||||
* Start Time:
|
* Start Time:
|
||||||
A parameter, <startTime>, used to trigger the replay feature
|
A parameter, <startTime>, used to trigger the replay feature
|
||||||
and indicate that the replay should start at the time
|
and indicate that the replay should start at the time
|
||||||
|
|
@ -474,42 +524,39 @@ stream_notify(clicon_handle h,
|
||||||
compliant to [RFC3339]. Implementations must support time
|
compliant to [RFC3339]. Implementations must support time
|
||||||
zones.
|
zones.
|
||||||
|
|
||||||
* Should we fork??
|
|
||||||
* Assume no future sample timestamps.
|
* Assume no future sample timestamps.
|
||||||
*/
|
*/
|
||||||
int
|
static int
|
||||||
stream_replay(clicon_handle h,
|
stream_replay_notify(clicon_handle h,
|
||||||
char *stream,
|
event_stream_t *es,
|
||||||
struct timeval *start,
|
struct stream_subscription *ss)
|
||||||
struct timeval *stop)
|
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
event_stream_t *es;
|
|
||||||
struct stream_replay *r;
|
struct stream_replay *r;
|
||||||
|
|
||||||
if ((es = stream_find(h, stream)) == NULL){
|
/* If <startTime> is not present, this is not a replay */
|
||||||
clicon_err(OE_CFG, ENOENT, "Stream %s not found", stream);
|
if (!timerisset(&ss->ss_starttime))
|
||||||
goto done;
|
goto ok;
|
||||||
}
|
|
||||||
if (!es->es_replay_enabled)
|
if (!es->es_replay_enabled)
|
||||||
goto ok;
|
goto ok;
|
||||||
|
/* Get replay linked list */
|
||||||
if ((r = es->es_replay) == NULL)
|
if ((r = es->es_replay) == NULL)
|
||||||
goto ok;
|
goto ok;
|
||||||
/* First loop to skip until start */
|
/* First loop to skip until start */
|
||||||
if (start){
|
do {
|
||||||
do {
|
if (timercmp(&r->r_tv, &ss->ss_starttime, >=))
|
||||||
if (timercmp(&r->r_tv, start, >=))
|
break;
|
||||||
break;
|
r = NEXTQ(struct stream_replay *, r);
|
||||||
r = NEXTQ(struct stream_replay *, r);
|
} while (r && r!=es->es_replay);
|
||||||
} while (r && r!=es->es_replay);
|
if (r == NULL)
|
||||||
if (r == NULL)
|
goto ok; /* No samples to replay */
|
||||||
goto ok;
|
|
||||||
}
|
|
||||||
/* Then notify until stop */
|
/* Then notify until stop */
|
||||||
do {
|
do {
|
||||||
if (stop && timercmp(&r->r_tv, stop, >))
|
if (timerisset(&ss->ss_stoptime) &&
|
||||||
|
timercmp(&r->r_tv, &ss->ss_stoptime, >))
|
||||||
break;
|
break;
|
||||||
if (stream_notify_xml(h, es, r->r_xml) < 0)
|
if ((*ss->ss_fn)(h, r->r_xml, ss->ss_arg) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
r = NEXTQ(struct stream_replay *, r);
|
r = NEXTQ(struct stream_replay *, r);
|
||||||
} while (r && r!=es->es_replay);
|
} while (r && r!=es->es_replay);
|
||||||
|
|
@ -545,6 +592,88 @@ stream_replay_add(event_stream_t *es,
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* tmp struct for timeout callback containing clicon handle,
|
||||||
|
* stream and subscription
|
||||||
|
*/
|
||||||
|
struct replay_arg{
|
||||||
|
clicon_handle ra_h;
|
||||||
|
char *ra_stream; /* Name of stream - malloced: free by cb */
|
||||||
|
stream_fn_t ra_fn; /* Stream callback */
|
||||||
|
void *ra_arg; /* Argument - typically unique client handle */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! Timeout callback for replaying stream
|
||||||
|
* @param[in] fd Ignore
|
||||||
|
* @param[in] arg tmp struct including clicon handle, stream and subscription
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
stream_replay_cb(int fd,
|
||||||
|
void *arg)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
struct replay_arg *ra= (struct replay_arg*)arg;
|
||||||
|
event_stream_t *es;
|
||||||
|
struct stream_subscription *ss;
|
||||||
|
|
||||||
|
if (ra == NULL)
|
||||||
|
goto ok;
|
||||||
|
if (ra->ra_stream == NULL)
|
||||||
|
goto ok;
|
||||||
|
if ((es = stream_find(ra->ra_h, ra->ra_stream)) == NULL)
|
||||||
|
goto ok;
|
||||||
|
if ((ss = stream_ss_find(es, ra->ra_fn, ra->ra_arg)) == NULL)
|
||||||
|
goto ok;
|
||||||
|
if (stream_replay_notify(ra->ra_h, es, ss) < 0)
|
||||||
|
goto done;
|
||||||
|
ok:
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
if (ra){
|
||||||
|
if (ra->ra_stream)
|
||||||
|
free(ra->ra_stream);
|
||||||
|
free(ra);
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Schedule stream replay to occur asap, eg "now"
|
||||||
|
*
|
||||||
|
* @param[in] h clicon handle
|
||||||
|
* @param[in] stream Name of stream
|
||||||
|
* @param[in] fn Stream callback
|
||||||
|
* @param[in] arg Argument - typically unique client handle
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
stream_replay_trigger(clicon_handle h,
|
||||||
|
char *stream,
|
||||||
|
stream_fn_t fn,
|
||||||
|
void *arg)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
struct timeval now;
|
||||||
|
struct replay_arg *ra;
|
||||||
|
|
||||||
|
if ((ra = malloc(sizeof(*ra))) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "malloc");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
memset(ra, 0, sizeof(*ra));
|
||||||
|
ra->ra_h = h;
|
||||||
|
if ((ra->ra_stream = strdup(stream)) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "strdup");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
ra->ra_fn = fn;
|
||||||
|
ra->ra_arg = arg;
|
||||||
|
gettimeofday(&now, NULL);
|
||||||
|
if (event_reg_timeout(now, stream_replay_cb, ra,
|
||||||
|
"create-subscribtion stream replay") < 0)
|
||||||
|
goto done;
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef CLIXON_PUBLISH_STREAMS
|
#ifdef CLIXON_PUBLISH_STREAMS
|
||||||
/* SSE support using Nginx Nchan. This code needs to be enabled at configure
|
/* SSE support using Nginx Nchan. This code needs to be enabled at configure
|
||||||
* time using: --enable-publish configure option
|
* time using: --enable-publish configure option
|
||||||
|
|
@ -649,8 +778,8 @@ url_post(char *url,
|
||||||
* Push via curl_post to publish stream event
|
* Push via curl_post to publish stream event
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] event Event as XML
|
* @param[in] event Event as XML
|
||||||
* @param[in] arg Extra argument provided in stream_cb_add
|
* @param[in] arg Extra argument provided in stream_ss_add
|
||||||
* @see stream_cb_add
|
* @see stream_ss_add
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
stream_publish_cb(clicon_handle h,
|
stream_publish_cb(clicon_handle h,
|
||||||
|
|
@ -711,7 +840,7 @@ stream_publish(clicon_handle h,
|
||||||
#ifdef CLIXON_PUBLISH_STREAMS
|
#ifdef CLIXON_PUBLISH_STREAMS
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
|
|
||||||
if (stream_cb_add(h, stream, NULL, stream_publish_cb, (void*)stream) < 0)
|
if (stream_ss_add(h, stream, NULL, stream_publish_cb, (void*)stream) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
|
|
||||||
|
|
@ -49,5 +49,8 @@ expecteof "$PROG" 0 '<x a="t"/>' '^<x a="t"/>$'
|
||||||
new "Single quotes for attributes (returns double quotes but at least parses right)"
|
new "Single quotes for attributes (returns double quotes but at least parses right)"
|
||||||
expecteof "$PROG" 0 "<x a='t'/>" '^<x a="t"/>$'
|
expecteof "$PROG" 0 "<x a='t'/>" '^<x a="t"/>$'
|
||||||
|
|
||||||
|
new "Mixed quotes"
|
||||||
|
expecteof "$PROG" 0 "<x a='t' b=\"q\"/>" '^<x a="t" b="q"/>$'
|
||||||
|
|
||||||
rm -rf $dir
|
rm -rf $dir
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue