* Stream replay support
* RFC8040 Restconf replay support: start-time and stop-time query parameter support
* This only applies to "native" restconf stream support, Nchan mode has different replay functionality
* RFC5277 Netconf replay support using <startTime> and
* Replay support is only in-memory and not persistent. External time-series DB could be added.
This commit is contained in:
parent
77e56868d4
commit
af16760279
13 changed files with 355 additions and 65 deletions
|
|
@ -33,6 +33,11 @@
|
||||||
* 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 (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
|
||||||
|
* Stream replay support
|
||||||
|
* RFC8040 Restconf replay support: start-time and stop-time query parameter support
|
||||||
|
* This only applies to "native" restconf stream support, Nchan mode has different replay functionality
|
||||||
|
* RFC5277 Netconf replay support using <startTime> and
|
||||||
|
* Replay support is only in-memory and not persistent. External time-series DB could be added.
|
||||||
|
|
||||||
### 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.
|
* clixon-config YAML file has new revision: 2018-10-21.
|
||||||
|
|
@ -94,7 +99,9 @@
|
||||||
* Set dir /www-data with www-data as owner, see https://github.com/clicon/clixon/issues/37
|
* Set dir /www-data with www-data as owner, see https://github.com/clicon/clixon/issues/37
|
||||||
|
|
||||||
### Known issues
|
### Known issues
|
||||||
* Bug: Top-level Yang symbol cannot be called "config" in any imported yang file.
|
* netconf rpc input is not sanity checked for wrong symbols (just ignored).
|
||||||
|
* Yang sub-command order and cardinality not checked.
|
||||||
|
* Top-level Yang symbol cannot be called "config" in any imported yang file.
|
||||||
* datastore uses "config" as reserved keyword for storing any XML whoich collides with code for detecting Yang sanity.
|
* datastore uses "config" as reserved keyword for storing any XML whoich collides with code for detecting Yang sanity.
|
||||||
* Namespace name relabeling is not supported.
|
* Namespace name relabeling is not supported.
|
||||||
* Eg: if "des" is defined as prefix for an imported module, then a relabeling using xmlfns is not supported, such as:
|
* Eg: if "des" is defined as prefix for an imported module, then a relabeling using xmlfns is not supported, such as:
|
||||||
|
|
|
||||||
|
|
@ -815,12 +815,13 @@ from_client_delete_config(clicon_handle h,
|
||||||
* @param[out] cbret Return xml value cligen buffer
|
* @param[out] cbret Return xml value cligen buffer
|
||||||
* @retval 0 OK
|
* @retval 0 OK
|
||||||
* @retval -1 Error. Send error message back to client.
|
* @retval -1 Error. Send error message back to client.
|
||||||
|
* @see RFC5277 2.1
|
||||||
* @example:
|
* @example:
|
||||||
* <create-subscription>
|
* <create-subscription>
|
||||||
* <stream>RESULT</stream> # If not present, events in the default NETCONF stream will be sent.
|
* <stream>RESULT</stream> # If not present, events in the default NETCONF stream will be sent.
|
||||||
* <filter type="xpath" select="XPATH-EXPR"/>
|
* <filter type="xpath" select="XPATH-EXPR"/>
|
||||||
* <startTime/> # only for replay (NYI)
|
* <startTime></startTime>
|
||||||
* <stopTime/> # only for replay (NYI)
|
* <stopTime></stopTime>
|
||||||
* </create-subscription>
|
* </create-subscription>
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
|
|
@ -834,10 +835,28 @@ from_client_create_subscription(clicon_handle h,
|
||||||
cxobj *x; /* Generic xml tree */
|
cxobj *x; /* Generic xml tree */
|
||||||
cxobj *xfilter; /* Filter xml tree */
|
cxobj *xfilter; /* Filter xml tree */
|
||||||
char *ftype;
|
char *ftype;
|
||||||
|
char *starttime = NULL;
|
||||||
|
char *stoptime = NULL;
|
||||||
char *selector = NULL;
|
char *selector = NULL;
|
||||||
|
struct timeval start;
|
||||||
|
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){
|
||||||
|
stoptime = xml_find_value(x, "body");
|
||||||
|
if (str2time(stoptime, &stop) < 0){
|
||||||
|
clicon_err(OE_PROTO, errno, "str2time");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((x = xpath_first(xe, "//startTime")) != NULL){
|
||||||
|
starttime = xml_find_value(x, "body");
|
||||||
|
if (str2time(starttime, &start) < 0){
|
||||||
|
clicon_err(OE_PROTO, errno, "str2time");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
if ((xfilter = xpath_first(xe, "//filter")) != NULL){
|
if ((xfilter = xpath_first(xe, "//filter")) != NULL){
|
||||||
if ((ftype = xml_find_value(xfilter, "type")) != NULL){
|
if ((ftype = xml_find_value(xfilter, "type")) != NULL){
|
||||||
/* Only accept xpath as filter type */
|
/* Only accept xpath as filter type */
|
||||||
|
|
@ -855,7 +874,12 @@ from_client_create_subscription(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
if (stream_cb_add(h, stream, selector, ce_event_cb, (void*)ce) < 0)
|
if (stream_cb_add(h, stream, selector, stoptime?&stop:NULL,
|
||||||
|
ce_event_cb, (void*)ce) < 0)
|
||||||
|
goto done;
|
||||||
|
/* Replay of this stream to specific subscription according to start and stop*/
|
||||||
|
if (starttime) /* XXX No this must be done _after_ this RPC completes */
|
||||||
|
if (stream_replay(h, stream, &start, stoptime?&stop:NULL) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
|
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
|
||||||
ok:
|
ok:
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,8 @@ backend_terminate(clicon_handle h)
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
if ((yspec = clicon_dbspec_yang(h)) != NULL)
|
if ((yspec = clicon_dbspec_yang(h)) != NULL)
|
||||||
yspec_free(yspec);
|
yspec_free(yspec);
|
||||||
|
if ((yspec = clicon_config_yang(h)) != NULL)
|
||||||
|
yspec_free(yspec);
|
||||||
if ((x = clicon_conf_xml(h)) != NULL)
|
if ((x = clicon_conf_xml(h)) != NULL)
|
||||||
xml_free(x);
|
xml_free(x);
|
||||||
stream_publish_exit();
|
stream_publish_exit();
|
||||||
|
|
@ -590,6 +592,7 @@ main(int argc,
|
||||||
usage(h, argv[0]);
|
usage(h, argv[0]);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
clicon_config_yang_set(h, yspecfg);
|
||||||
/* External NACM file? */
|
/* External NACM file? */
|
||||||
nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE");
|
nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE");
|
||||||
if (nacm_mode && strcmp(nacm_mode, "external") == 0)
|
if (nacm_mode && strcmp(nacm_mode, "external") == 0)
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,8 @@ cli_terminate(clicon_handle h)
|
||||||
clicon_rpc_close_session(h);
|
clicon_rpc_close_session(h);
|
||||||
if ((yspec = clicon_dbspec_yang(h)) != NULL)
|
if ((yspec = clicon_dbspec_yang(h)) != NULL)
|
||||||
yspec_free(yspec);
|
yspec_free(yspec);
|
||||||
|
if ((yspec = clicon_config_yang(h)) != NULL)
|
||||||
|
yspec_free(yspec);
|
||||||
if ((x = clicon_conf_xml(h)) != NULL)
|
if ((x = clicon_conf_xml(h)) != NULL)
|
||||||
xml_free(x);
|
xml_free(x);
|
||||||
cli_plugin_finish(h);
|
cli_plugin_finish(h);
|
||||||
|
|
@ -338,7 +340,7 @@ main(int argc, char **argv)
|
||||||
usage(h, argv[0]);
|
usage(h, argv[0]);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
clicon_config_yang_set(h, yspecfg);
|
||||||
/* Now rest of options */
|
/* Now rest of options */
|
||||||
opterr = 0;
|
opterr = 0;
|
||||||
optind = 1;
|
optind = 1;
|
||||||
|
|
|
||||||
|
|
@ -280,6 +280,8 @@ netconf_terminate(clicon_handle h)
|
||||||
clicon_rpc_close_session(h);
|
clicon_rpc_close_session(h);
|
||||||
if ((yspec = clicon_dbspec_yang(h)) != NULL)
|
if ((yspec = clicon_dbspec_yang(h)) != NULL)
|
||||||
yspec_free(yspec);
|
yspec_free(yspec);
|
||||||
|
if ((yspec = clicon_config_yang(h)) != NULL)
|
||||||
|
yspec_free(yspec);
|
||||||
if ((x = clicon_conf_xml(h)) != NULL)
|
if ((x = clicon_conf_xml(h)) != NULL)
|
||||||
xml_free(x);
|
xml_free(x);
|
||||||
event_exit();
|
event_exit();
|
||||||
|
|
@ -390,7 +392,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)
|
||||||
return -1;
|
return -1;
|
||||||
|
clicon_config_yang_set(h, yspecfg);
|
||||||
/* Now rest of options */
|
/* Now rest of options */
|
||||||
optind = 1;
|
optind = 1;
|
||||||
opterr = 0;
|
opterr = 0;
|
||||||
|
|
|
||||||
|
|
@ -454,6 +454,8 @@ restconf_terminate(clicon_handle h)
|
||||||
clicon_rpc_close_session(h);
|
clicon_rpc_close_session(h);
|
||||||
if ((yspec = clicon_dbspec_yang(h)) != NULL)
|
if ((yspec = clicon_dbspec_yang(h)) != NULL)
|
||||||
yspec_free(yspec);
|
yspec_free(yspec);
|
||||||
|
if ((yspec = clicon_config_yang(h)) != NULL)
|
||||||
|
yspec_free(yspec);
|
||||||
if ((x = clicon_conf_xml(h)) != NULL)
|
if ((x = clicon_conf_xml(h)) != NULL)
|
||||||
xml_free(x);
|
xml_free(x);
|
||||||
clicon_handle_exit(h);
|
clicon_handle_exit(h);
|
||||||
|
|
@ -583,6 +585,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;
|
||||||
|
clicon_config_yang_set(h, yspecfg);
|
||||||
stream_path = clicon_option_str(h, "CLICON_STREAM_PATH");
|
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;
|
||||||
|
|
|
||||||
|
|
@ -301,7 +301,7 @@ clixon_plugin_init(clicon_handle h)
|
||||||
* 2) setup timer for notifications, so something happens on stream
|
* 2) setup timer for notifications, so something happens on stream
|
||||||
* 3) setup stream callbacks for notification to push channel
|
* 3) setup stream callbacks for notification to push channel
|
||||||
*/
|
*/
|
||||||
if (stream_register(h, "EXAMPLE", "Example event stream") < 0)
|
if (stream_register(h, "EXAMPLE", "Example event stream", 1) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
/* assumes: CLIXON_PUBLISH_STREAMS, eg configure --enable-publish
|
/* assumes: CLIXON_PUBLISH_STREAMS, eg configure --enable-publish
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,11 @@ int clicon_quiet_mode_set(clicon_handle h, int val);
|
||||||
yang_spec * clicon_dbspec_yang(clicon_handle h);
|
yang_spec * clicon_dbspec_yang(clicon_handle h);
|
||||||
int clicon_dbspec_yang_set(clicon_handle h, struct yang_spec *ys);
|
int clicon_dbspec_yang_set(clicon_handle h, struct yang_spec *ys);
|
||||||
|
|
||||||
|
#if 1 /* Temporary function until "Top-level Yang symbol cannot be called "config"" is fixed */
|
||||||
|
yang_spec * clicon_config_yang(clicon_handle h);
|
||||||
|
int clicon_config_yang_set(clicon_handle h, struct yang_spec *ys);
|
||||||
|
#endif
|
||||||
|
|
||||||
cxobj *clicon_conf_xml(clicon_handle h);
|
cxobj *clicon_conf_xml(clicon_handle h);
|
||||||
int clicon_conf_xml_set(clicon_handle h, cxobj *x);
|
int clicon_conf_xml_set(clicon_handle h, cxobj *x);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,16 +37,29 @@
|
||||||
#ifndef _CLIXON_QUEUE_H_
|
#ifndef _CLIXON_QUEUE_H_
|
||||||
#define _CLIXON_QUEUE_H_
|
#define _CLIXON_QUEUE_H_
|
||||||
|
|
||||||
/*
|
/*! Circular queue structure for use as first entry in a parent structure.
|
||||||
* Circular queue structure for use as first entry in a parent structure.
|
* Add qelem_t as first element in struct
|
||||||
|
* @code
|
||||||
|
* struct a{
|
||||||
|
* qelem_t a_q; # this must be there
|
||||||
|
* int a_b; # other elements
|
||||||
|
* ...
|
||||||
|
* };
|
||||||
|
* @endcode
|
||||||
*/
|
*/
|
||||||
typedef struct _qelem_t {
|
typedef struct _qelem_t {
|
||||||
struct _qelem_t *q_next;
|
struct _qelem_t *q_next;
|
||||||
struct _qelem_t *q_prev;
|
struct _qelem_t *q_prev;
|
||||||
} qelem_t;
|
} qelem_t;
|
||||||
|
|
||||||
/*
|
/*! Append element 'elem' to queue.
|
||||||
* Append element 'elem' to queue.
|
* @param[in] elem Element to be added
|
||||||
|
* @param[in,out] pred Add element after this
|
||||||
|
* @code
|
||||||
|
* struct a *list; # existing list
|
||||||
|
* struct a *new = malloc(...);
|
||||||
|
* ADDQ(new, list);
|
||||||
|
* @endcode
|
||||||
*/
|
*/
|
||||||
#define ADDQ(elem, pred) { \
|
#define ADDQ(elem, pred) { \
|
||||||
register qelem_t *Xe = (qelem_t *) (elem); \
|
register qelem_t *Xe = (qelem_t *) (elem); \
|
||||||
|
|
@ -62,8 +75,14 @@ typedef struct _qelem_t {
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*! Insert element 'elem' in queue after 'pred'
|
||||||
* Insert element 'elem' in queue after 'pred'
|
* @param[in] elem Element to be added
|
||||||
|
* @param[in,out] pred Add element after this
|
||||||
|
* @code
|
||||||
|
* struct a *list; # existing list
|
||||||
|
* struct a *new = malloc(...);
|
||||||
|
* INSQ(new, list);
|
||||||
|
* @endcode
|
||||||
*/
|
*/
|
||||||
#define INSQ(elem, pred) { \
|
#define INSQ(elem, pred) { \
|
||||||
register qelem_t *Xe = (qelem_t *) (elem); \
|
register qelem_t *Xe = (qelem_t *) (elem); \
|
||||||
|
|
@ -79,9 +98,16 @@ typedef struct _qelem_t {
|
||||||
pred = elem; \
|
pred = elem; \
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*! Remove element 'elem' from queue. 'head' is the pointer to the queue and
|
||||||
* Remove element 'elem' from queue. 'head' is the pointer to the queue and
|
|
||||||
* is of 'type'.
|
* is of 'type'.
|
||||||
|
* @param[in] elem
|
||||||
|
* @param[in] head
|
||||||
|
* @param[in] type XXX needed?
|
||||||
|
* @code
|
||||||
|
* struct a *list; # existing list
|
||||||
|
* struct a *el; # remove this
|
||||||
|
* DELQ(el, list, struct a*);
|
||||||
|
* @endcode
|
||||||
*/
|
*/
|
||||||
#define DELQ(elem, head, type) { \
|
#define DELQ(elem, head, type) { \
|
||||||
register qelem_t *Xe = (qelem_t *) elem; \
|
register qelem_t *Xe = (qelem_t *) elem; \
|
||||||
|
|
@ -92,10 +118,13 @@ typedef struct _qelem_t {
|
||||||
head = (type)Xe->q_next; \
|
head = (type)Xe->q_next; \
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*! Get next entry in list
|
||||||
* Get next entry in list
|
* @param[in] type Type of element
|
||||||
|
* @param[in] el Return next element after elem.
|
||||||
|
* @code
|
||||||
|
* struct a *list; # existing element (or list)
|
||||||
|
* NEXTQ(struct a*, el);
|
||||||
*/
|
*/
|
||||||
#define NEXTQ(type, elem) ((type)((elem)?((qelem_t *)(elem))->q_next:NULL))
|
#define NEXTQ(type, elem) ((type)((elem)?((qelem_t *)(elem))->q_next:NULL))
|
||||||
|
|
||||||
|
|
||||||
#endif /* _CLIXON_QUEUE_H_ */
|
#endif /* _CLIXON_QUEUE_H_ */
|
||||||
|
|
|
||||||
|
|
@ -51,10 +51,18 @@ struct stream_subscription{
|
||||||
struct stream_subscription *ss_next;
|
struct stream_subscription *ss_next;
|
||||||
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_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 */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Replay time-series */
|
||||||
|
struct stream_replay{
|
||||||
|
qelem_t r_q; /* queue header */
|
||||||
|
struct timeval r_tv; /* time index */
|
||||||
|
cxobj *r_xml; /* event in xml form */
|
||||||
|
};
|
||||||
|
|
||||||
/* See RFC8040 9.3, stream list, no replay support for now
|
/* See RFC8040 9.3, stream list, no replay support for now
|
||||||
*/
|
*/
|
||||||
struct event_stream{
|
struct event_stream{
|
||||||
|
|
@ -62,6 +70,8 @@ struct event_stream{
|
||||||
char *es_name; /* name of notification event stream */
|
char *es_name; /* name of notification event stream */
|
||||||
char *es_description;
|
char *es_description;
|
||||||
struct stream_subscription *es_subscription;
|
struct stream_subscription *es_subscription;
|
||||||
|
int es_replay_enabled; /* set if replay is enables */
|
||||||
|
struct stream_replay *es_replay;
|
||||||
};
|
};
|
||||||
typedef struct event_stream event_stream_t;
|
typedef struct event_stream event_stream_t;
|
||||||
|
|
||||||
|
|
@ -69,25 +79,26 @@ typedef struct event_stream event_stream_t;
|
||||||
* Prototypes
|
* Prototypes
|
||||||
*/
|
*/
|
||||||
event_stream_t *stream_find(clicon_handle h, const char *name);
|
event_stream_t *stream_find(clicon_handle h, const char *name);
|
||||||
int stream_register(clicon_handle h, const char *name, const char *description);
|
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);
|
||||||
int stream_cb_add(clicon_handle h, char *stream, char *xpath, stream_fn_t fn, void *arg);
|
struct stream_subscription *stream_cb_add(clicon_handle h, char *stream,
|
||||||
|
char *xpath, struct timeval *stop, stream_fn_t fn, void *arg);
|
||||||
int stream_cb_delete(clicon_handle h, char *stream, stream_fn_t fn, void *arg);
|
int stream_cb_delete(clicon_handle h, char *stream, stream_fn_t fn, void *arg);
|
||||||
int stream_notify_xml(clicon_handle h, char *stream, cxobj *xevent);
|
int stream_notify_xml(clicon_handle h, event_stream_t *es, 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
|
||||||
|
|
||||||
/* Experimental publish streams using SSE */
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
/* 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);
|
||||||
int stream_publish_init();
|
int stream_publish_init();
|
||||||
int stream_publish_exit();
|
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)
|
|
||||||
|
|
||||||
#endif /* _CLIXON_STREAM_H_ */
|
#endif /* _CLIXON_STREAM_H_ */
|
||||||
|
|
|
||||||
|
|
@ -588,6 +588,40 @@ clicon_dbspec_yang_set(clicon_handle h,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if 1 /* Temporary function until "Top-level Yang symbol cannot be called "config"" is fixed */
|
||||||
|
/*! Get YANG specification for clixon config
|
||||||
|
* Must use hash functions directly since they are not strings.
|
||||||
|
*/
|
||||||
|
yang_spec *
|
||||||
|
clicon_config_yang(clicon_handle h)
|
||||||
|
{
|
||||||
|
clicon_hash_t *cdat = clicon_data(h);
|
||||||
|
size_t len;
|
||||||
|
void *p;
|
||||||
|
|
||||||
|
if ((p = hash_value(cdat, "control_yang", &len)) != NULL)
|
||||||
|
return *(yang_spec **)p;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Set yang specification for control
|
||||||
|
* ys must be a malloced pointer
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
clicon_config_yang_set(clicon_handle h,
|
||||||
|
struct yang_spec *ys)
|
||||||
|
{
|
||||||
|
clicon_hash_t *cdat = clicon_data(h);
|
||||||
|
|
||||||
|
/* It is the pointer to ys that should be copied by hash,
|
||||||
|
so we send a ptr to the ptr to indicate what to copy.
|
||||||
|
*/
|
||||||
|
if (hash_add(cdat, "control_yang", &ys, sizeof(ys)) == NULL)
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/*! Get YANG specification for Clixon system options and features
|
/*! Get YANG specification for Clixon system options and features
|
||||||
* Must use hash functions directly since they are not strings.
|
* Must use hash functions directly since they are not strings.
|
||||||
* Example: features are typically accessed directly in the config tree.
|
* Example: features are typically accessed directly in the config tree.
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,11 @@
|
||||||
***** END LICENSE BLOCK *****
|
***** END LICENSE BLOCK *****
|
||||||
|
|
||||||
* Event notification streams according to RFC5277
|
* Event notification streams according to RFC5277
|
||||||
* See (old) subscription code in clixon_backend_handle.c and backend_client.c
|
* The stream implementation has three parts:
|
||||||
|
* 1) Base stream handling: stream_find/register/delete_all/get_xml
|
||||||
|
* 2) Callback and notification handling (stream_cb_add/delete/timeout, stream_notify, etc
|
||||||
|
* 3) Stream replay: stream_replay/_add
|
||||||
|
* 4) nginx/nchan publish code (use --enable-publish config option)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifdef HAVE_CONFIG_H
|
#ifdef HAVE_CONFIG_H
|
||||||
|
|
@ -88,7 +92,8 @@ stream_find(clicon_handle h,
|
||||||
int
|
int
|
||||||
stream_register(clicon_handle h,
|
stream_register(clicon_handle h,
|
||||||
const char *name,
|
const char *name,
|
||||||
const char *description)
|
const char *description,
|
||||||
|
const int replay_enabled)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
event_stream_t *es;
|
event_stream_t *es;
|
||||||
|
|
@ -108,6 +113,7 @@ stream_register(clicon_handle h,
|
||||||
clicon_err(OE_XML, errno, "strdup");
|
clicon_err(OE_XML, errno, "strdup");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
es->es_replay_enabled = replay_enabled;
|
||||||
clicon_stream_append(h, es);
|
clicon_stream_append(h, es);
|
||||||
ok:
|
ok:
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
@ -122,6 +128,7 @@ int
|
||||||
stream_delete_all(event_stream_t *es)
|
stream_delete_all(event_stream_t *es)
|
||||||
{
|
{
|
||||||
event_stream_t *e_next;
|
event_stream_t *e_next;
|
||||||
|
struct stream_replay *r;
|
||||||
|
|
||||||
while (es){
|
while (es){
|
||||||
e_next = es->es_next;
|
e_next = es->es_next;
|
||||||
|
|
@ -129,13 +136,19 @@ 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 ((r = es->es_replay) != NULL){
|
||||||
|
if (r->r_xml)
|
||||||
|
xml_free(r->r_xml);
|
||||||
|
DELQ(r, es->es_replay, struct stream_replay *);
|
||||||
|
free(r);
|
||||||
|
}
|
||||||
free(es);
|
free(es);
|
||||||
es = e_next;
|
es = e_next;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Return stream definition
|
/*! Return stream definition state in XML supporting RFC 8040 and RFC5277
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] access If set, include access/location
|
* @param[in] access If set, include access/location
|
||||||
* @param[out] cb Output buffer containing XML on exit
|
* @param[out] cb Output buffer containing XML on exit
|
||||||
|
|
@ -189,21 +202,22 @@ stream_del()
|
||||||
* @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] 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, ie no such stream
|
* @retval -1 Error, ie no such stream
|
||||||
*/
|
*/
|
||||||
int
|
struct stream_subscription *
|
||||||
stream_cb_add(clicon_handle h,
|
stream_cb_add(clicon_handle h,
|
||||||
char *stream,
|
char *stream,
|
||||||
char *xpath,
|
char *xpath,
|
||||||
|
struct timeval *stoptime,
|
||||||
stream_fn_t fn,
|
stream_fn_t fn,
|
||||||
void *arg)
|
void *arg)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
|
||||||
event_stream_t *es;
|
event_stream_t *es;
|
||||||
struct stream_subscription *ss;
|
struct stream_subscription *ss = NULL;
|
||||||
|
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
if ((es = stream_find(h, stream)) == NULL){
|
if ((es = stream_find(h, stream)) == NULL){
|
||||||
|
|
@ -219,6 +233,8 @@ stream_cb_add(clicon_handle h,
|
||||||
clicon_err(OE_CFG, errno, "strdup");
|
clicon_err(OE_CFG, errno, "strdup");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
if (stoptime)
|
||||||
|
ss->ss_stoptime = *stoptime;
|
||||||
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;
|
||||||
|
|
@ -227,9 +243,11 @@ stream_cb_add(clicon_handle h,
|
||||||
ss->ss_arg = arg;
|
ss->ss_arg = arg;
|
||||||
ss->ss_next = es->es_subscription;
|
ss->ss_next = es->es_subscription;
|
||||||
es->es_subscription = ss;
|
es->es_subscription = ss;
|
||||||
retval = 0;
|
return ss;
|
||||||
done:
|
done:
|
||||||
return retval;
|
if (ss)
|
||||||
|
free(ss);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Delete event notification callback to a stream given a callback and arg
|
/*! Delete event notification callback to a stream given a callback and arg
|
||||||
|
|
@ -240,25 +258,18 @@ stream_cb_add(clicon_handle h,
|
||||||
* @retval 0 OK
|
* @retval 0 OK
|
||||||
* @retval -1 Error
|
* @retval -1 Error
|
||||||
*/
|
*/
|
||||||
int
|
static int
|
||||||
stream_cb_delete(clicon_handle h,
|
stream_cb_rm(event_stream_t *es,
|
||||||
char *stream,
|
struct stream_subscription *ssrm)
|
||||||
stream_fn_t fn,
|
|
||||||
void *arg)
|
|
||||||
{
|
{
|
||||||
int retval = -1;
|
|
||||||
event_stream_t *es;
|
|
||||||
struct stream_subscription **ss_prev;
|
struct stream_subscription **ss_prev;
|
||||||
struct stream_subscription *ss;
|
struct stream_subscription *ss;
|
||||||
struct stream_subscription *ss_next;
|
struct stream_subscription *ss_next;
|
||||||
|
|
||||||
for (es=clicon_stream(h); es; es=es->es_next){
|
|
||||||
if (stream && strcmp(stream, es->es_name)!=0)
|
|
||||||
continue;
|
|
||||||
ss_prev = &es->es_subscription;
|
ss_prev = &es->es_subscription;
|
||||||
for (ss = *ss_prev; ss; ss = ss_next){
|
for (ss = *ss_prev; ss; ss = ss_next){
|
||||||
ss_next = ss->ss_next;
|
ss_next = ss->ss_next;
|
||||||
if (fn == ss->ss_fn && arg == ss->ss_arg){
|
if (ss == ssrm){
|
||||||
*ss_prev = ss->ss_next;
|
*ss_prev = ss->ss_next;
|
||||||
if (ss->ss_stream)
|
if (ss->ss_stream)
|
||||||
free(ss->ss_stream);
|
free(ss->ss_stream);
|
||||||
|
|
@ -270,8 +281,56 @@ stream_cb_delete(clicon_handle h,
|
||||||
}
|
}
|
||||||
ss_prev = &ss->ss_next;
|
ss_prev = &ss->ss_next;
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Remove stream subscription identified with fn and arg
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
stream_cb_delete(clicon_handle h,
|
||||||
|
char *stream,
|
||||||
|
stream_fn_t fn,
|
||||||
|
void *arg)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
event_stream_t *es;
|
||||||
|
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;
|
retval = 0;
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Check if any stream subscriptions have timed out, if so remove them
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
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 again;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -285,25 +344,22 @@ stream_cb_delete(clicon_handle h,
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
stream_notify_xml(clicon_handle h,
|
stream_notify_xml(clicon_handle h,
|
||||||
char *stream,
|
event_stream_t *es,
|
||||||
cxobj *xevent)
|
cxobj *xevent)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
event_stream_t *es;
|
|
||||||
struct stream_subscription *ss;
|
struct stream_subscription *ss;
|
||||||
|
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
if ((es = stream_find(h, stream)) == NULL)
|
|
||||||
goto ok;
|
|
||||||
/* Go thru all global (handle) subscriptions and find matches */
|
/* Go thru all global (handle) subscriptions and find matches */
|
||||||
for (ss = es->es_subscription; ss; ss = ss->ss_next){
|
for (ss = es->es_subscription; ss; ss = ss->ss_next){
|
||||||
|
/* Check if this is of interest, ie not larger than stoptime? */
|
||||||
if (ss->ss_xpath == NULL ||
|
if (ss->ss_xpath == NULL ||
|
||||||
strlen(ss->ss_xpath)==0 ||
|
strlen(ss->ss_xpath)==0 ||
|
||||||
xpath_first(xevent, "%s", ss->ss_xpath) != NULL)
|
xpath_first(xevent, "%s", ss->ss_xpath) != NULL)
|
||||||
if ((*ss->ss_fn)(h, xevent, ss->ss_arg) < 0)
|
if ((*ss->ss_fn)(h, xevent, ss->ss_arg) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
ok:
|
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
return retval;
|
return retval;
|
||||||
|
|
@ -335,8 +391,11 @@ stream_notify(clicon_handle h,
|
||||||
cbuf *cb = NULL;
|
cbuf *cb = NULL;
|
||||||
char timestr[27];
|
char timestr[27];
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
|
event_stream_t *es;
|
||||||
|
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
|
if ((es = stream_find(h, stream)) == NULL)
|
||||||
|
goto ok;
|
||||||
va_start(args, event);
|
va_start(args, event);
|
||||||
len = vsnprintf(NULL, 0, event, args) + 1;
|
len = vsnprintf(NULL, 0, event, args) + 1;
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
@ -352,12 +411,18 @@ 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;
|
||||||
|
|
@ -367,8 +432,14 @@ 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, stream, xev) < 0)
|
if (stream_notify_xml(h, es, xev) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
if (es->es_replay_enabled){
|
||||||
|
if (stream_replay_add(es, &tv, xev) < 0)
|
||||||
|
goto done;
|
||||||
|
xev = NULL; /* xml stored in replay_add and should not be freed */
|
||||||
|
}
|
||||||
|
ok:
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
if (cb)
|
if (cb)
|
||||||
|
|
@ -380,7 +451,105 @@ stream_notify(clicon_handle h,
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*! Replay a stream
|
||||||
|
* Start Time:
|
||||||
|
A parameter, <startTime>, used to trigger the replay feature
|
||||||
|
and indicate that the replay should start at the time
|
||||||
|
specified. If <startTime> is not present, this is not a replay
|
||||||
|
subscription. It is not valid to specify start times that are
|
||||||
|
later than the current time. If the <startTime> specified is
|
||||||
|
earlier than the log can support, the replay will begin with
|
||||||
|
the earliest available notification. This parameter is of type
|
||||||
|
dateTime and compliant to [RFC3339]. Implementations must
|
||||||
|
support time zones.
|
||||||
|
|
||||||
|
Stop Time:
|
||||||
|
An optional parameter, <stopTime>, used with the optional
|
||||||
|
replay feature to indicate the newest notifications of
|
||||||
|
interest. If <stopTime> is not present, the notifications will
|
||||||
|
continue until the subscription is terminated. Must be used
|
||||||
|
with and be later than <startTime>. Values of <stopTime> in
|
||||||
|
the future are valid. This parameter is of type dateTime and
|
||||||
|
compliant to [RFC3339]. Implementations must support time
|
||||||
|
zones.
|
||||||
|
|
||||||
|
* Should we fork??
|
||||||
|
* Assume no future sample timestamps.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
stream_replay(clicon_handle h,
|
||||||
|
char *stream,
|
||||||
|
struct timeval *start,
|
||||||
|
struct timeval *stop)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
event_stream_t *es;
|
||||||
|
struct stream_replay *r;
|
||||||
|
|
||||||
|
if ((es = stream_find(h, stream)) == NULL){
|
||||||
|
clicon_err(OE_CFG, ENOENT, "Stream %s not found", stream);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (!es->es_replay_enabled)
|
||||||
|
goto ok;
|
||||||
|
if ((r = es->es_replay) == NULL)
|
||||||
|
goto ok;
|
||||||
|
/* First loop to skip until start */
|
||||||
|
if (start){
|
||||||
|
do {
|
||||||
|
if (timercmp(&r->r_tv, start, >=))
|
||||||
|
break;
|
||||||
|
r = NEXTQ(struct stream_replay *, r);
|
||||||
|
} while (r && r!=es->es_replay);
|
||||||
|
if (r == NULL)
|
||||||
|
goto ok;
|
||||||
|
}
|
||||||
|
/* Then notify until stop */
|
||||||
|
do {
|
||||||
|
if (stop && timercmp(&r->r_tv, stop, >))
|
||||||
|
break;
|
||||||
|
if (stream_notify_xml(h, es, r->r_xml) < 0)
|
||||||
|
goto done;
|
||||||
|
r = NEXTQ(struct stream_replay *, r);
|
||||||
|
} while (r && r!=es->es_replay);
|
||||||
|
ok:
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Add replay sample to stream with timestamp
|
||||||
|
* @param[in] es Stream
|
||||||
|
* @param[in] tv Timestamp
|
||||||
|
* @param[in] xv XML
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
stream_replay_add(event_stream_t *es,
|
||||||
|
struct timeval *tv,
|
||||||
|
cxobj *xv)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
struct stream_replay *new;
|
||||||
|
|
||||||
|
if ((new = malloc(sizeof *new)) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "malloc");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
memset(new, 0, (sizeof *new));
|
||||||
|
new->r_tv = *tv;
|
||||||
|
new->r_xml = xv;
|
||||||
|
ADDQ(new, es->es_replay);
|
||||||
|
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
|
||||||
|
* time using: --enable-publish configure option
|
||||||
|
* It uses CURL and autoconf needs to set that dependency
|
||||||
|
*/
|
||||||
|
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
|
@ -412,7 +581,6 @@ curl_get_cb(void *ptr,
|
||||||
memcpy(buf->b_buf+buf->b_len, ptr, len);
|
memcpy(buf->b_buf+buf->b_len, ptr, len);
|
||||||
buf->b_len += len;
|
buf->b_len += len;
|
||||||
buf->b_buf[buf->b_len] = '\0';
|
buf->b_buf[buf->b_len] = '\0';
|
||||||
fprintf(stderr, "%s: %s\n", __FUNCTION__, buf->b_buf);
|
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -581,5 +749,3 @@ stream_publish_exit()
|
||||||
#endif
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,7 @@ fi
|
||||||
# Netconf stream subscription
|
# Netconf stream subscription
|
||||||
# Switch here since subscriptions takes time
|
# Switch here since subscriptions takes time
|
||||||
if true; then
|
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
|
||||||
|
|
||||||
|
|
@ -152,6 +153,9 @@ new "netconf NONEXIST subscription"
|
||||||
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
|
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
|
fi
|
||||||
|
|
||||||
|
#new "netconf EXAMPLE subscription with replay"
|
||||||
|
#expectwait "$clixon_netconf -qf $cfg -y $fyang" '<rpc><create-subscription><stream>EXAMPLE</stream><startTime>2018-10-21T19:22:16</startTime><stopTime>2018-10-21T19:25:00</stopTime></create-subscription></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]><notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>20' 5
|
||||||
|
|
||||||
new "Kill restconf daemon"
|
new "Kill restconf daemon"
|
||||||
sudo pkill -u www-data clixon_restconf
|
sudo pkill -u www-data clixon_restconf
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue