From 98f3cd0e32858cf176e78e1382833b28e6dbaf73 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 30 Sep 2018 14:51:30 +0200 Subject: [PATCH] * Major rewrite of event streams * If you used old event callbacks API, you need to switch to the streams API * See clixon_stream.[ch] * Old streams API which needs to be removed include: * clicon_log_register_callback() * subscription_add() --> stream_register() * backend_notify() and backend_notify_xml() - use stream_notify() instead * Example uses "NETCONF" stream instead of "ROUTING" * Added timeout option -t for clixon_netconf - quit after max time. --- CHANGELOG.md | 18 +- apps/backend/backend_client.c | 100 ++++------- apps/backend/backend_client.h | 11 -- apps/backend/backend_main.c | 46 ----- apps/backend/clixon_backend_handle.c | 255 --------------------------- apps/backend/clixon_backend_handle.h | 25 --- apps/netconf/netconf_main.c | 32 +++- apps/netconf/netconf_rpc.c | 36 ++-- apps/restconf/README.md | 13 +- apps/restconf/restconf_lib.h | 6 + apps/restconf/restconf_main.c | 14 +- apps/restconf/restconf_stream.c | 139 ++++++++++++++- datastore/text/clixon_xmldb_text.c | 2 +- doc/FAQ.md | 7 +- example/example_backend.c | 31 ++-- example/example_cli.cli | 4 +- lib/clixon/clixon.h.in | 4 +- lib/clixon/clixon_log.h | 8 - lib/clixon/clixon_proto.h | 2 +- lib/clixon/clixon_stream.h | 18 +- lib/src/clixon_handle.c | 2 +- lib/src/clixon_log.c | 65 ++----- lib/src/clixon_proto.c | 39 +++- lib/src/clixon_proto_client.c | 4 +- lib/src/clixon_stream.c | 183 ++++++++++++++++--- lib/src/clixon_xml.c | 5 +- test/lib.sh | 23 ++- test/test_auth_ext.sh | 26 +-- test/test_event.sh | 45 +++-- test/test_netconf.sh | 24 ++- test/test_restconf.sh | 45 +++-- 31 files changed, 597 insertions(+), 635 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9a1e5ec..064f7bcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,17 +11,23 @@ * Not supported: notification, deviation, module-set-id, etc. * Enabled by default, disable by resetting CLICON_MODULE_LIBRARY_RFC7895 * Yang 1.1 notification support (RFC 7950: Sec 7.16) -* Event stream discovery support according to RFC 5277 for netconf - * Implemented by ietf-netconf-notification.yang - * Disabled by default. Enable by setting CLICON_STREAM_DISCOVERY_RFC5277 -* Event stream discovery support according to RFC 8040 for restconf - * Implemented by ietf-restconf-monitoring.yang (mimics schema in 3.2.5.1) - * Disabled by default. Enable by setting CLICON_STREAM_DISCOVERY_RFC8040. +* Major rewrite of event streams + * If you used old event callbacks API, you need to switch to the streams API + * See clixon_stream.[ch] + * Old streams API which needs to be removed include: + * clicon_log_register_callback() + * subscription_add() --> stream_register() + * backend_notify() and backend_notify_xml() - use stream_notify() instead + * 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. + * Example uses "NETCONF" stream instead of "ROUTING" * clixon_restconf and clixon_netconf now take -D as command-line option instead of just -D * This aligns to clixon_cli and clixon_backend * Application command option -S to clixon_netconf is obsolete. Use `clixon_netconf -l s` instead. ### Minor changes + +* Added timeout option -t for clixon_netconf - quit after max time. * Comply to RFC 8040 3.5.3.1 rule: api-identifier = [module-name ":"] identifier * The "module-name" was a no-op before. * This means that there was no difference between eg: GET /restconf/data/ietf-yang-library:modules-state and GET /restconf/data/XXXX:modules-state diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 189e2dc5..8dac8c7d 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -69,59 +69,6 @@ #include "backend_client.h" #include "backend_handle.h" -/*! Add client notification subscription. Ie send notify to this client when event occurs - * @param[in] ce Client entry struct - * @param[in] stream Notification stream name - * @param[in] format How to display event (see enum format_enum) - * @param[in] filter Filter, what to display, eg xpath for format=xml, fnmatch - * - * @see backend_notify - where subscription is made and notify call is made - */ -static struct client_subscription * -client_subscription_add(struct client_entry *ce, - char *stream, - enum format_enum format, - char *filter) -{ - struct client_subscription *su = NULL; - - if ((su = malloc(sizeof(*su))) == NULL){ - clicon_err(OE_PLUGIN, errno, "malloc"); - goto done; - } - memset(su, 0, sizeof(*su)); - su->su_stream = strdup(stream); - su->su_format = format; - su->su_filter = filter?strdup(filter):strdup(""); - su->su_next = ce->ce_subscription; - ce->ce_subscription = su; - done: - return su; -} - -/*! Delete stream subscription from client subscription list */ -static int -client_subscription_delete(struct client_entry *ce, - struct client_subscription *su0) -{ - struct client_subscription *su; - struct client_subscription **su_prev; - - su_prev = &ce->ce_subscription; /* this points to stack and is not real backpointer */ - for (su = *su_prev; su; su = su->su_next){ - if (su == su0){ - *su_prev = su->su_next; - free(su->su_stream); - if (su->su_filter) - free(su->su_filter); - free(su); - break; - } - su_prev = &su->su_next; - } - return 0; -} - static struct client_entry * ce_find_bypid(struct client_entry *ce_list, int pid) @@ -134,6 +81,31 @@ ce_find_bypid(struct client_entry *ce_list, return NULL; } +static int +ce_event_cb(clicon_handle h, + void *event, + void *arg) +{ + struct client_entry *ce = (struct client_entry *)arg; + + if (send_msg_notify_xml(ce->ce_s, (cxobj*)event) < 0){ + if (errno == ECONNRESET || errno == EPIPE){ + clicon_log(LOG_WARNING, "client %d reset", ce->ce_nr); +#if 0 + /* We should remove here but removal is not possible + from a client since backend_client is not linked. + Maybe we should add it to the plugin, but it feels + "safe" that you cant remove a client. + Instead, the client is (hopefully) removed elsewhere? + */ + backend_client_rm(h, ce); +#endif + } + + } + return 0; +} + /*! Remove client entry state * Close down everything wrt clients (eg sockets, subscriptions) * Finally actually remove client struct in handle @@ -148,7 +120,6 @@ backend_client_rm(clicon_handle h, struct client_entry *c; struct client_entry *c0; struct client_entry **ce_prev; - struct client_subscription *su; c0 = backend_client_list(h); ce_prev = &c0; /* this points to stack and is not real backpointer */ @@ -159,8 +130,8 @@ backend_client_rm(clicon_handle h, close(ce->ce_s); ce->ce_s = 0; } - while ((su = ce->ce_subscription) != NULL) - client_subscription_delete(ce, su); + /* for all streams */ + stream_cb_delete(h, NULL, ce_event_cb, (void*)ce); break; } ce_prev = &c->ce_next; @@ -900,6 +871,7 @@ from_client_delete_config(clicon_handle h, return retval; } + /*! Internal message: Create subscription for notifications see RFC 5277 * @param[in] h Clicon handle * @param[in] xe Netconf request xml tree @@ -910,7 +882,7 @@ from_client_delete_config(clicon_handle h, * @example: * * RESULT # If not present, events in the default NETCONF stream will be sent. - * XPATH-EXPR<(filter> + * * # only for replay (NYI) * # only for replay (NYI) * @@ -922,24 +894,27 @@ from_client_create_subscription(clicon_handle h, cbuf *cbret) { char *stream = "NETCONF"; - char *filter = NULL; int retval = -1; - cxobj *x; /* Genereic xml tree */ + cxobj *x; /* Generic xml tree */ + cxobj *xfilter; /* Filter xml tree */ char *ftype; + char *selector = NULL; if ((x = xpath_first(xe, "//stream")) != NULL) stream = xml_find_value(x, "body"); - if ((x = xpath_first(xe, "//filter")) != NULL){ - if ((ftype = xml_find_value(x, "type")) != NULL){ + if ((xfilter = xpath_first(xe, "//filter")) != NULL){ + if ((ftype = xml_find_value(xfilter, "type")) != NULL){ /* Only accept xpath as filter type */ if (strcmp(ftype, "xpath") != 0){ if (netconf_operation_failed(cbret, "application", "Only xpath filter type supported")< 0) goto done; goto ok; } + if ((selector = xml_find_value(xfilter, "select")) == NULL) + goto done; } } - if (client_subscription_add(ce, stream, FORMAT_XML, filter) == NULL) + if (stream_cb_add(h, stream, selector, ce_event_cb, (void*)ce) < 0) goto done; cprintf(cbret, ""); ok: @@ -1334,6 +1309,7 @@ from_client_msg(clicon_handle h, } else if (strcmp(name, "close-session") == 0){ xmldb_unlock_all(h, pid); + stream_cb_delete(h, NULL, ce_event_cb, (void*)ce); cprintf(cbret, ""); } else if (strcmp(name, "kill-session") == 0){ diff --git a/apps/backend/backend_client.h b/apps/backend/backend_client.h index 191a700a..81620d4a 100644 --- a/apps/backend/backend_client.h +++ b/apps/backend/backend_client.h @@ -52,19 +52,8 @@ struct client_entry{ int ce_pid; /* Process id */ int ce_uid; /* User id of calling process */ clicon_handle ce_handle; /* clicon config handle (all clients have same?) */ - struct client_subscription *ce_subscription; /* notification subscriptions */ }; -/* Notification subscription info - * @see subscription in config_handle.c - */ -struct client_subscription{ - struct client_subscription *su_next; - int su_s; /* stream socket */ - enum format_enum su_format; /* format of notification stream */ - char *su_stream; - char *su_filter; -}; /* * Prototypes diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 6640dce3..053b6d98 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -98,7 +98,6 @@ backend_terminate(clicon_handle h) xmldb_plugin_unload(h); /* unload storage plugin */ backend_handle_exit(h); /* Cannot use h after this */ event_exit(); - clicon_log_register_callback(NULL, NULL); clicon_debug(1, "%s done", __FUNCTION__); clicon_log_exit(); return 0; @@ -191,7 +190,6 @@ db_merge(clicon_handle h, return retval; } - /*! Create backend server socket and register callback */ static int @@ -211,45 +209,6 @@ server_socket(clicon_handle h) return ss; } -/*! Callback for CLICON log events - * If you make a subscription to CLICON stream, this function is called for every - * log event. - */ -static int -backend_log_cb(int level, - char *msg, - void *arg) -{ - int retval = -1; - size_t n; - char *ptr; - char *nptr; - char *newmsg = NULL; - - /* backend_notify() will go through all clients and see if any has - registered "CLICON", and if so make a clicon_proto notify message to - those clients. - Sanitize '%' into "%%" to prevent segvfaults in vsnprintf later. - At this stage all formatting is already done */ - n = 0; - for(ptr=msg; *ptr; ptr++) - if (*ptr == '%') - n++; - if ((newmsg = malloc(strlen(msg) + n + 1)) == NULL) { - clicon_err(OE_UNIX, errno, "malloc"); - return -1; - } - for(ptr=msg, nptr=newmsg; *ptr; ptr++) { - *nptr++ = *ptr; - if (*ptr == '%') - *nptr++ = '%'; - } - retval = backend_notify(arg, "CLICON", level, newmsg); - free(newmsg); - - return retval; -} - /*! Call plugin_start with -- user options */ static int plugin_start_useroptions(clicon_handle h, @@ -758,8 +717,6 @@ main(int argc, if (stream_register(h, "NETCONF", "default NETCONF event stream") < 0) goto done; - if (stream_register(h, "CLICON", "Clicon logs") < 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"); @@ -850,9 +807,6 @@ main(int argc, if ((pid = pidfile_write(pidfile)) < 0) goto done; - /* Register log notifications */ - if (clicon_log_register_callback(backend_log_cb, h) < 0) - goto done; clicon_log(LOG_NOTICE, "%s: %u Started", __PROGRAM__, getpid()); if (set_signal(SIGTERM, backend_sig_term, NULL) < 0){ clicon_err(OE_DEMON, errno, "Setting signal"); diff --git a/apps/backend/clixon_backend_handle.c b/apps/backend/clixon_backend_handle.c index feb7969c..98dbc3e3 100644 --- a/apps/backend/clixon_backend_handle.c +++ b/apps/backend/clixon_backend_handle.c @@ -91,7 +91,6 @@ struct backend_handle { /* ------ end of common handle ------ */ struct client_entry *bh_ce_list; /* The client list */ int bh_ce_nr; /* Number of clients, just increment */ - struct handle_subscription *bh_subscription; /* Event subscription list */ cxobj *bh_nacm; /* NACM external struct */ }; @@ -121,155 +120,6 @@ backend_handle_exit(clicon_handle h) return 0; } -/*! Notify event and distribute to all registered clients - * - * @param[in] h Clicon handle - * @param[in] stream Name of event stream. CLICON is predefined as LOG stream - * @param[in] level Event level (not used yet) - * @param[in] event Actual message as text format - * - * Stream is a string used to qualify the event-stream. Distribute the - * event to all clients registered to this backend. - * XXX: event-log NYI. - * @see also subscription_add() - * @see also backend_notify_xml() - */ -int -backend_notify(clicon_handle h, - char *stream, - int level, - char *event) -{ - struct client_entry *ce; - struct client_entry *ce_next; - struct client_subscription *su; - struct handle_subscription *hs; - int retval = -1; - - clicon_debug(2, "%s %s", __FUNCTION__, stream); - /* First thru all clients(sessions), and all subscriptions and find matches */ - for (ce = backend_client_list(h); ce; ce = ce_next){ - ce_next = ce->ce_next; - for (su = ce->ce_subscription; su; su = su->su_next) - if (strcmp(su->su_stream, stream) == 0){ - if (strlen(su->su_filter)==0 || fnmatch(su->su_filter, event, 0) == 0){ - if (send_msg_notify(ce->ce_s, level, event) < 0){ - if (errno == ECONNRESET || errno == EPIPE){ - clicon_log(LOG_WARNING, "client %d reset", ce->ce_nr); -#if 0 - /* We should remove here but removal is not possible - from a client since backend_client is not linked. - Maybe we should add it to the plugin, but it feels - "safe" that you cant remove a client. - Instead, the client is (hopefully) removed elsewhere? - */ - backend_client_rm(h, ce); -#endif - break; - } - goto done; - } - } - } - } - /* Then go thru all global (handle) subscriptions and find matches */ - hs = NULL; - while ((hs = subscription_each(h, hs)) != NULL){ - if (hs->hs_format != FORMAT_TEXT) - continue; - if (strcmp(hs->hs_stream, stream)) - continue; - if (hs->hs_filter==NULL || - strlen(hs->hs_filter)==0 || - fnmatch(hs->hs_filter, event, 0) == 0) - if ((*hs->hs_fn)(h, event, hs->hs_arg) < 0) - goto done; - } - retval = 0; - done: - return retval; -} - -/*! Notify event and distribute to all registered clients - * - * @param[in] h Clicon handle - * @param[in] stream Name of event stream. CLICON is predefined as LOG stream - * @param[in] level Event level (not used yet) - * @param[in] x Actual message as xml tree - * - * Stream is a string used to qualify the event-stream. Distribute the - * event to all clients registered to this backend. - * XXX: event-log NYI. - * @see also subscription_add() - * @see also backend_notify() - */ -int -backend_notify_xml(clicon_handle h, - char *stream, - int level, - cxobj *x) -{ - struct client_entry *ce; - struct client_entry *ce_next; - struct client_subscription *su; - int retval = -1; - cbuf *cb = NULL; - struct handle_subscription *hs; - - clicon_debug(1, "%s %s", __FUNCTION__, stream); - /* Now go thru all clients(sessions), and all subscriptions and find matches */ - for (ce = backend_client_list(h); ce; ce = ce_next){ - ce_next = ce->ce_next; - for (su = ce->ce_subscription; su; su = su->su_next) - if (strcmp(su->su_stream, stream) == 0){ - if (strlen(su->su_filter)==0 || xpath_first(x, "%s", su->su_filter) != NULL){ - if (cb==NULL){ - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_PLUGIN, errno, "cbuf_new"); - goto done; - } - if (clicon_xml2cbuf(cb, x, 0, 0) < 0) - goto done; - } - if (send_msg_notify(ce->ce_s, level, cbuf_get(cb)) < 0){ - if (errno == ECONNRESET || errno == EPIPE){ - clicon_log(LOG_WARNING, "client %d reset", ce->ce_nr); -#if 0 - /* We should remove here but removal is not possible - from a client since backend_client is not linked. - Maybe we should add it to the plugin, but it feels - "safe" that you cant remove a client. - Instead, the client is (hopefully) removed elsewhere? - */ - backend_client_rm(h, ce); -#endif - break; - } - goto done; - } - } - } - } - /* Then go thru all global (handle) subscriptions and find matches */ - hs = NULL; - while ((hs = subscription_each(h, hs)) != NULL){ - if (hs->hs_format != FORMAT_XML) - continue; - if (strcmp(hs->hs_stream, stream)) - continue; - if (strlen(hs->hs_filter)==0 || xpath_first(x, "%s", hs->hs_filter) != NULL){ - if ((*hs->hs_fn)(h, x, hs->hs_arg) < 0) - goto done; - } - } - retval = 0; - done: - if (cb) - cbuf_free(cb); - return retval; - -} - /*! Add new client, typically frontend such as cli, netconf, restconf * @param[in] h Clicon handle * @param[in] addr Address of client @@ -332,111 +182,6 @@ backend_client_delete(clicon_handle h, return 0; } -/*! Add subscription given stream name, callback and argument - * @param[in] h Clicon handle - * @param[in] stream Name of event stream - * @param[in] format Expected format of event, eg text or xml - * @param[in] filter Filter to match event, depends on format, eg xpath for xml - * @param[in] fn Callback when event occurs - * @param[in] arg Argument to use with callback. Also handle when deleting - * Note that arg is not a real handle. - * @see subscription_delete - * @see subscription_each - */ -struct handle_subscription * -subscription_add(clicon_handle h, - char *stream, - enum format_enum format, - char *filter, - subscription_fn_t fn, - void *arg) -{ - struct backend_handle *bh = handle(h); - struct handle_subscription *hs = NULL; - - if ((hs = malloc(sizeof(*hs))) == NULL){ - clicon_err(OE_PLUGIN, errno, "malloc"); - goto done; - } - memset(hs, 0, sizeof(*hs)); - hs->hs_stream = strdup(stream); - hs->hs_format = format; - hs->hs_filter = filter?strdup(filter):NULL; - hs->hs_next = bh->bh_subscription; - hs->hs_fn = fn; - hs->hs_arg = arg; - bh->bh_subscription = hs; - done: - return hs; -} - -/*! Delete subscription given stream name, callback and argument - * @param[in] h Clicon handle - * @param[in] stream Name of event stream - * @param[in] fn Callback when event occurs - * @param[in] arg Argument to use with callback and handle - * Note that arg is not a real handle. - * @see subscription_add - * @see subscription_each - */ -int -subscription_delete(clicon_handle h, - char *stream, - subscription_fn_t fn, - void *arg) -{ - struct backend_handle *bh = handle(h); - struct handle_subscription *hs; - struct handle_subscription **hs_prev; - - hs_prev = &bh->bh_subscription; /* this points to stack and is not real backpointer */ - for (hs = *hs_prev; hs; hs = hs->hs_next){ - /* XXX arg == hs->hs_arg */ - if (strcmp(hs->hs_stream, stream)==0 && hs->hs_fn == fn){ - *hs_prev = hs->hs_next; - free(hs->hs_stream); - if (hs->hs_filter) - free(hs->hs_filter); - if (hs->hs_arg) - free(hs->hs_arg); - free(hs); - break; - } - hs_prev = &hs->hs_next; - } - return 0; -} - -/*! Iterator over subscriptions - * - * NOTE: Never manipulate the child-list during operation or using the - * same object recursively, the function uses an internal field to remember the - * index used. It works as long as the same object is not iterated concurrently. - * - * @param[in] h clicon handle - * @param[in] hprev iterator, initialize with NULL - * @code - * clicon_handle h; - * struct handle_subscription *hs = NULL; - * while ((hs = subscription_each(h, hs)) != NULL) { - * ... - * } - * @endcode - */ -struct handle_subscription * -subscription_each(clicon_handle h, - struct handle_subscription *hprev) -{ - struct backend_handle *bh = handle(h); - struct handle_subscription *hs = NULL; - - if (hprev) - hs = hprev->hs_next; - else - hs = bh->bh_subscription; - return hs; -} - int backend_nacm_list_set(clicon_handle h, cxobj *xnacm) diff --git a/apps/backend/clixon_backend_handle.h b/apps/backend/clixon_backend_handle.h index 45b17d30..bdfbb425 100644 --- a/apps/backend/clixon_backend_handle.h +++ b/apps/backend/clixon_backend_handle.h @@ -43,34 +43,9 @@ /* * Types */ -/* Notification subscription info - * @see client_subscription in config_client.h - */ -struct handle_subscription{ - struct handle_subscription *hs_next; - enum format_enum hs_format; /* format (enum format_enum) XXX not needed? */ - char *hs_stream; /* name of notify stream */ - char *hs_filter; /* filter, if format=xml: xpath, if text: fnmatch */ - subscription_fn_t hs_fn; /* Callback when event occurs */ - void *hs_arg; /* Callback argument */ -}; /* * Prototypes */ -/* Log for netconf notify function (config_client.c) */ -int backend_notify(clicon_handle h, char *stream, int level, char *txt); -int backend_notify_xml(clicon_handle h, char *stream, int level, cxobj *x); - - -struct handle_subscription *subscription_add(clicon_handle h, char *stream, - enum format_enum format, char *filter, - subscription_fn_t fn, void *arg); - -int subscription_delete(clicon_handle h, char *stream, - subscription_fn_t fn, void *arg); - -struct handle_subscription *subscription_each(clicon_handle h, - struct handle_subscription *hprev); #endif /* _CLIXON_BACKEND_HANDLE_H_ */ diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index 77db0176..4fa50b29 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -71,7 +71,7 @@ #include "netconf_rpc.h" /* Command line options to be passed to getopt(3) */ -#define NETCONF_OPTS "hDf:l:qa:u:d:y:U:" +#define NETCONF_OPTS "hD:f:l:qa:u:d:y:U:t:" #define NETCONF_LOGFILE "/tmp/clixon_netconf.log" @@ -284,6 +284,13 @@ netconf_terminate(clicon_handle h) return 0; } +static int +timeout_fn(int s, + void *arg) +{ + clicon_err(OE_EVENTS, ETIME, "User request timeout"); + return -1; +} /*! Usage help routine * @param[in] h Clicon handle @@ -305,7 +312,8 @@ usage(clicon_handle h, "\t-d \tSpecify netconf plugin directory dir (default: %s)\n" "\t-y \tOverride yang spec file (dont include .yang suffix)\n" - "\t-U \tOver-ride unix user with a pseudo user for NACM.\n", + "\t-U \tOver-ride unix user with a pseudo user for NACM.\n" + "\t-t \tTimeout in seconds. Quit after this time.\n", argv0, clicon_netconf_dir(h) ); @@ -324,12 +332,13 @@ main(int argc, char *dir; int logdst = CLICON_LOG_STDERR; struct passwd *pw; - - /* In the startup, logs to stderr & debug flag set later */ - clicon_log_init(__PROGRAM__, LOG_INFO, logdst); + struct timeval tv = {0,}; /* timeout */ + /* Create handle */ if ((h = clicon_handle_init()) == NULL) return -1; + /* In the startup, logs to stderr & debug flag set later */ + clicon_log_init(__PROGRAM__, LOG_INFO, logdst); /* Set username to clicon handle. Use in all communication to backend */ if ((pw = getpwuid(getuid())) == NULL){ @@ -338,7 +347,6 @@ main(int argc, } if (clicon_username_set(h, pw->pw_name) < 0) goto done; - while ((c = getopt(argc, argv, NETCONF_OPTS)) != -1) switch (c) { case 'h' : /* help */ @@ -362,6 +370,7 @@ main(int argc, goto done; break; } + /* * Logs, error and debug to stderr or syslog, set debug level */ @@ -408,6 +417,10 @@ main(int argc, if (clicon_username_set(h, optarg) < 0) goto done; break; + case 't': /* timeout in seconds */ + tv.tv_sec = atoi(optarg); + break; + default: usage(h, argv[0]); break; @@ -440,6 +453,13 @@ main(int argc, goto done; if (debug) clicon_option_dump(h, debug); + if (tv.tv_sec || tv.tv_usec){ + struct timeval t; + gettimeofday(&t, NULL); + timeradd(&t, &tv, &t); + if (event_reg_timeout(t, timeout_fn, NULL, "timeout") < 0) + goto done; + } if (event_loop() < 0) goto done; done: diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index da50636e..ec534063 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -733,20 +733,14 @@ static int netconf_notification_cb(int s, void *arg) { - cxobj *xfilter = (cxobj *)arg; - char *selector; struct clicon_msg *reply = NULL; int eof; - char *event = NULL; int retval = -1; cbuf *cb; - cxobj *xe = NULL; /* event xml */ + cxobj *xn = NULL; /* event xml */ cxobj *xt = NULL; /* top xml */ - if (0){ - fprintf(stderr, "%s\n", __FUNCTION__); /* debug */ - xml_print(stderr, xfilter); /* debug */ - } + clicon_debug(1, "%s", __FUNCTION__); /* get msg (this is the reason this function is called) */ if (clicon_msg_rcv(s, &reply, &eof) < 0) goto done; @@ -756,31 +750,20 @@ netconf_notification_cb(int s, close(s); errno = ESHUTDOWN; event_unreg_fd(s, netconf_notification_cb); - if (xfilter) - xml_free(xfilter); goto done; } if (clicon_msg_decode(reply, &xt) < 0) goto done; - if ((xe = xpath_first(xt, "//event")) != NULL) - event = xml_body(xe); - - /* parse event */ - if (0){ /* XXX CLICON events are not xml */ - /* find and apply filter */ - if ((selector = xml_find_value(xfilter, "select")) == NULL) - goto done; - if (xpath_first(xe, "%s", selector) == NULL) { - fprintf(stderr, "%s no match\n", __FUNCTION__); /* debug */ - } - } + if ((xn = xpath_first(xt, "notification")) == NULL) + goto ok; /* create netconf message */ if ((cb = cbuf_new()) == NULL){ clicon_err(OE_PLUGIN, errno, "cbuf_new"); goto done; } add_preamble(cb); /* Make it well-formed netconf xml */ - cprintf(cb, "%s", event); + if (clicon_xml2cbuf(cb, xn, 0, 0) < 0) + goto done; add_postamble(cb); /* Send it to listening client on stdout */ if (netconf_output(1, cb, "notification") < 0){ @@ -789,6 +772,7 @@ netconf_notification_cb(int s, } fflush(stdout); cbuf_free(cb); +ok: retval = 0; done: if (xt != NULL) @@ -802,7 +786,7 @@ netconf_notification_cb(int s, RESULT # If not present, events in the default NETCONF stream will be sent. - XPATH-EXPR<(filter> + # only for replay (NYI) # only for replay (NYI) @@ -810,7 +794,7 @@ netconf_notification_cb(int s, * @param[in] h clicon handle * @param[in] xn Sub-tree (under xorig) at ... level. * @param[out] xret Return XML, error or OK - + * @see netconf_notification_cb for asynchronous stream notifications */ static int netconf_create_subscription(clicon_handle h, @@ -840,7 +824,7 @@ netconf_create_subscription(clicon_handle h, goto done; if (event_reg_fd(s, netconf_notification_cb, - xfilter?xml_dup(xfilter):NULL, + NULL, "notification socket") < 0) goto done; ok: diff --git a/apps/restconf/README.md b/apps/restconf/README.md index c07b0178..c8654187 100644 --- a/apps/restconf/README.md +++ b/apps/restconf/README.md @@ -13,10 +13,15 @@ Define nginx config file: /etc/nginx/sites-available/default ``` server { ... - location / { - root /usr/share/nginx/html/restconf; - fastcgi_pass unix:/www-data/fastcgi_restconf.sock; - include fastcgi_params; + location /restconf { + fastcgi_pass unix:/www-data/fastcgi_restconf.sock; + include fastcgi_params; + } + location /stream { # for restconf notifications + fastcgi_pass unix:/www-data/fastcgi_restconf.sock; + include fastcgi_params; + proxy_http_version 1.1; + proxy_set_header Connection ""; } } ``` diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index cf8ef66b..c01ae7f6 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -36,6 +36,12 @@ #ifndef _RESTCONF_LIB_H_ #define _RESTCONF_LIB_H_ +/* + * Constants + */ +#define RESTCONF_API "restconf" +#define RESTCONF_STREAM "stream" + /* * Prototypes (also in clixon_restconf.h) */ diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 9f1ac618..a308315c 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -89,12 +89,6 @@ */ #define RESTCONF_WELL_KNOWN "/.well-known/host-meta" -#define RESTCONF_API "restconf" -#define RESTCONF_API_ROOT "/restconf" -#define RESTCONF_STREAM_ROOT "/stream" - -#define RESTCONF_LOGFILE "/www-data/clixon_restconf.log" - /*! Generic REST method, GET, PUT, DELETE, etc * @param[in] h CLIXON handle * @param[in] r Fastcgi request handle @@ -531,9 +525,11 @@ main(int argc, /* In the startup, logs to stderr & debug flag set later */ clicon_log_init(__PROGRAM__, LOG_INFO, logdst); + /* Create handle */ if ((h = clicon_handle_init()) == NULL) goto done; + _CLICON_HANDLE = h; /* for termination handling */ while ((c = getopt(argc, argv, RESTCONF_OPTS)) != -1) switch (c) { @@ -634,6 +630,8 @@ main(int argc, yang_spec_append(h, CLIXON_DATADIR, "ietf-yang-library", NULL)< 0) goto done; + if (stream_register(h, "NETCONF", "default NETCONF event stream") < 0) + goto done; /* Call start function in all plugins before we go interactive Pass all args after the standard options to plugin_start */ @@ -667,9 +665,9 @@ main(int argc, clicon_debug(1, "------------"); if ((path = FCGX_GetParam("REQUEST_URI", r->envp)) != NULL){ clicon_debug(1, "path: %s", path); - if (strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)) == 0) + if (strncmp(path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0) api_restconf(h, r); /* This is the function */ - else if (strncmp(path, RESTCONF_STREAM_ROOT, strlen(RESTCONF_STREAM_ROOT)) == 0) { + else if (strncmp(path, "/" RESTCONF_STREAM, strlen("/" RESTCONF_STREAM)) == 0) { api_stream(h, r); } else if (strncmp(path, RESTCONF_WELL_KNOWN, strlen(RESTCONF_WELL_KNOWN)) == 0) { diff --git a/apps/restconf/restconf_stream.c b/apps/restconf/restconf_stream.c index 5a99c702..71482434 100644 --- a/apps/restconf/restconf_stream.c +++ b/apps/restconf/restconf_stream.c @@ -64,6 +64,32 @@ #include /* Need to be after clixon_xml-h due to attribute format */ +static int +restconf_stream(clicon_handle h, + FCGX_Request *r, + event_stream_t *es) +{ + int retval = -1; + + clicon_debug(1, "%s", __FUNCTION__); + + FCGX_SetExitStatus(201, r->out); /* Created */ + FCGX_FPrintF(r->out, "Content-Type: text/event-stream\r\n"); + FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n"); + FCGX_FPrintF(r->out, "Connection: keep-alive\r\n"); + FCGX_FPrintF(r->out, "X-Accel-Buffering: no\r\n"); + FCGX_FPrintF(r->out, "\r\n"); + FCGX_FPrintF(r->out, "Here is output\r\n"); + FCGX_FPrintF(r->out, "\r\n"); + FCGX_FFlush(r->out); + sync(); + sleep(1); + FCGX_FPrintF(r->out, "Here is output 2\r\n"); + retval = 0; + // done: + return retval; +} + /* restconf */ #include "restconf_lib.h" #include "restconf_stream.h" @@ -73,13 +99,122 @@ */ int api_stream(clicon_handle h, - FCGX_Request *r) + FCGX_Request *r) { int retval = -1; + char *path; + char *query; + char *method; + char **pvec = NULL; + int pn; + cvec *qvec = NULL; + cvec *dvec = NULL; + cvec *pcvec = NULL; /* for rest api */ + cbuf *cb = NULL; + char *data; + int authenticated = 0; + char *media_accept; + char *media_content_type; + int pretty; + int parse_xml = 0; /* By default expect and parse JSON */ + int use_xml = 0; /* By default use JSON */ + cbuf *cbret = NULL; + cxobj *xret = NULL; + cxobj *xerr; + event_stream_t *es; clicon_debug(1, "%s", __FUNCTION__); + path = FCGX_GetParam("REQUEST_URI", r->envp); + query = FCGX_GetParam("QUERY_STRING", r->envp); + pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); + /* get xml/json in put and output */ + media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); + if (media_accept && strcmp(media_accept, "application/yang-data+xml")==0) + use_xml++; + media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp); + if (media_content_type && + strcmp(media_content_type, "application/yang-data+xml")==0) + parse_xml++; + if ((pvec = clicon_strsep(path, "/", &pn)) == NULL) + goto done; + /* Sanity check of path. Should be /stream/ */ + if (pn != 3){ + notfound(r); + goto ok; + } + if (strlen(pvec[0]) != 0){ + retval = notfound(r); + goto done; + } + if (strcmp(pvec[1], RESTCONF_STREAM)){ + retval = notfound(r); + goto done; + } + test(r, 1); + if ((method = pvec[2]) == NULL){ + retval = notfound(r); + goto done; + } + clicon_debug(1, "%s: method=%s", __FUNCTION__, method); + if (str2cvec(query, '&', '=', &qvec) < 0) + goto done; + if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */ + goto done; + /* data */ + if ((cb = readdata(r)) == NULL) + goto done; + data = cbuf_get(cb); + clicon_debug(1, "%s DATA=%s", __FUNCTION__, data); + if (str2cvec(data, '&', '=', &dvec) < 0) + goto done; + + /* If present, check credentials. See "plugin_credentials" in plugin + * See RFC 8040 section 2.5 + */ + if ((authenticated = clixon_plugin_auth(h, r)) < 0) + goto done; + clicon_debug(1, "%s auth:%d %s", __FUNCTION__, authenticated, clicon_username_get(h)); + + /* If set but no user, we set a dummy user */ + if (authenticated){ + if (clicon_username_get(h) == NULL) + clicon_username_set(h, "none"); + } + else{ + if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0) + goto done; + if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; + goto ok; + } + goto ok; + } + clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h)); + if ((es = stream_find(h, method)) == NULL){ + retval = notfound(r); + goto done; + } + if (restconf_stream(h, r, es) < 0) + goto done; + ok: retval = 0; - // done: + done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (pvec) + free(pvec); + if (dvec) + cvec_free(dvec); + if (qvec) + cvec_free(qvec); + if (pcvec) + cvec_free(pcvec); + if (cb) + cbuf_free(cb); + if (cbret) + cbuf_free(cbret); + if (xret) + xml_free(xret); + return retval; } diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index 894cf7c7..299c2f22 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -892,7 +892,7 @@ text_put(xmldb_handle xh, } cbretlocal++; } - if ((yspec = th->th_yangspec) == NULL){ + if ((yspec = th->th_yangspec) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; } diff --git a/doc/FAQ.md b/doc/FAQ.md index 50c68dac..2d1800b3 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -164,7 +164,7 @@ Example: ## How do I use notifications? -The example has a prebuilt notification stream called "ROUTING" that triggers every 10s. +The example has a prebuilt notification stream called "NETCONF" that triggers every 5s. You enable the notification either via the cli: ``` cli> notify @@ -173,10 +173,9 @@ cli> or via netconf: ``` clixon_netconf -qf /usr/local/etc/example.xml -ROUTING]]>]]> +NETCONF]]>]]> ]]>]]> -Routing notification]]>]]> -Routing notification]]>]]> +2018-09-30T12:44:59.657276faultEthernet0major]]>]]> ... ``` diff --git a/example/example_backend.c b/example/example_backend.c index 4db69f71..334ddf15 100644 --- a/example/example_backend.c +++ b/example/example_backend.c @@ -99,7 +99,8 @@ notification_timer(int fd, int retval = -1; clicon_handle h = (clicon_handle)arg; - if (backend_notify(h, "ROUTING", 0, "Routing notification") < 0) + /* XXX Change to actual netconf notifications */ + if (stream_notify(h, "NETCONF", "faultEthernet0major") < 0) goto done; if (notification_timer_setup(h) < 0) goto done; @@ -108,7 +109,7 @@ notification_timer(int fd, return retval; } -/*! Set up routing notifcation timer +/*! Set up routing notification timer */ static int notification_timer_setup(clicon_handle h) @@ -116,7 +117,7 @@ notification_timer_setup(clicon_handle h) struct timeval t, t1; gettimeofday(&t, NULL); - t1.tv_sec = 10; t1.tv_usec = 0; + t1.tv_sec = 5; t1.tv_usec = 0; timeradd(&t, &t1, &t); return event_reg_timeout(t, notification_timer, h, "notification timer"); } @@ -176,8 +177,15 @@ empty(clicon_handle h, /* Clicon handle */ * @retval 0 OK * @retval -1 Error * @see xmldb_get - * @note this example code returns a static statedata used in testing. - * Real code would poll state + * @note this example code returns requires this yang snippet: + container state { + config false; + description "state data for example application"; + leaf-list op { + type string; + } + } + * */ int example_statedata(clicon_handle h, @@ -187,12 +195,13 @@ example_statedata(clicon_handle h, int retval = -1; cxobj **xvec = NULL; - /* Example of (static) statedata, real code would poll state */ - if (xml_parse_string("" - "eth0" - "ex:eth" - "42" - "", NULL, &xstate) < 0) + /* Example of (static) statedata, real code would poll state + * Note this state needs to be accomanied by yang snippet + * above + */ + if (xml_parse_string("" + "42" + "", NULL, &xstate) < 0) goto done; retval = 0; done: diff --git a/example/example_cli.cli b/example/example_cli.cli index 3703e8ba..c4ccc4d1 100644 --- a/example/example_cli.cli +++ b/example/example_cli.cli @@ -64,7 +64,7 @@ load("Load configuration from XML file") ("Filename (local file } example("This is a comment") ("Just a random number"), mycallback("myarg"); rpc("ex:fib-route rpc") ("routing instance"), fib_route_rpc("myarg"); -notify("Get notifications from backend"), cli_notify("ROUTING", "1", "text"); -no("Negate") notify("Get notifications from backend"), cli_notify("ROUTING", "0", "xml"); +notify("Get notifications from backend"), cli_notify("NETCONF", "1", "text"); +no("Negate") notify("Get notifications from backend"), cli_notify("NETCONF", "0", "xml"); lock,cli_lock("candidate"); unlock,cli_unlock("candidate"); \ No newline at end of file diff --git a/lib/clixon/clixon.h.in b/lib/clixon/clixon.h.in index cd21a594..448387c4 100644 --- a/lib/clixon/clixon.h.in +++ b/lib/clixon/clixon.h.in @@ -63,12 +63,11 @@ #define LIBCLIXON_API 1 #include -#include #include #include #include #include -#include +#include #include #include #include @@ -76,6 +75,7 @@ #include #include #include +#include #include #include #include diff --git a/lib/clixon/clixon_log.h b/lib/clixon/clixon_log.h index 4524bcd7..7db79142 100644 --- a/lib/clixon/clixon_log.h +++ b/lib/clixon/clixon_log.h @@ -46,11 +46,6 @@ #define CLICON_LOG_STDOUT 4 /* print logs on stdout */ #define CLICON_LOG_FILE 8 /* print logs on clicon_log_filename */ -/* - * Types - */ -typedef int (clicon_log_notify_t)(int level, char *msg, void *arg); - /* * Variables */ @@ -64,7 +59,6 @@ int clicon_log_exit(void); int clicon_log_opt(char c); int clicon_log_file(char *filename); int clicon_get_logflags(void); -int clicon_log_str(int level, char *msg); #if defined(__GNUC__) && __GNUC__ >= 3 int clicon_log(int level, char *format, ...) __attribute__ ((format (printf, 2, 3))); int clicon_debug(int dbglevel, char *format, ...) __attribute__ ((format (printf, 2, 3))); @@ -72,9 +66,7 @@ int clicon_debug(int dbglevel, char *format, ...) __attribute__ ((format (printf int clicon_log(int level, char *format, ...); int clicon_debug(int dbglevel, char *format, ...); #endif -clicon_log_notify_t *clicon_log_register_callback(clicon_log_notify_t *cb, void *arg); int clicon_debug_init(int dbglevel, FILE *f); - char *mon2name(int md); #endif /* _CLIXON_LOG_H_ */ diff --git a/lib/clixon/clixon_proto.h b/lib/clixon/clixon_proto.h index fda6ba07..e3290c3f 100644 --- a/lib/clixon/clixon_proto.h +++ b/lib/clixon/clixon_proto.h @@ -87,7 +87,7 @@ int clicon_msg_send(int s, struct clicon_msg *msg); int clicon_msg_rcv(int s, struct clicon_msg **msg, int *eof); -int send_msg_notify(int s, int level, char *event); +int send_msg_notify_xml(int s, cxobj *xev); int send_msg_reply(int s, char *data, uint32_t datalen); diff --git a/lib/clixon/clixon_stream.h b/lib/clixon/clixon_stream.h index c2d2ba17..9e47426a 100644 --- a/lib/clixon/clixon_stream.h +++ b/lib/clixon/clixon_stream.h @@ -40,12 +40,12 @@ * Types */ /* subscription callback */ -typedef int (*stream_fn_t)(clicon_handle, void *filter, void *arg); -typedef stream_fn_t subscription_fn_t; +typedef int (*stream_fn_t)(clicon_handle h, void *event, void *arg); struct stream_subscription{ struct stream_subscription *ss_next; char *ss_stream; /* Name of associated stream */ + char *ss_xpath; /* Filter selector as xpath */ stream_fn_t ss_fn; /* Callback when event occurs */ void *ss_arg; /* Callback argument */ }; @@ -55,7 +55,7 @@ struct stream_subscription{ struct event_stream{ struct event_stream *es_next; char *es_name; /* name of notification event stream */ - char *es_description; + char *es_description; struct stream_subscription *es_subscription; }; typedef struct event_stream event_stream_t; @@ -65,9 +65,15 @@ typedef struct event_stream event_stream_t; */ 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_free(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_cb_add(clicon_handle h, char *stream, stream_fn_t fn, void *arg); -int stream_cb_delete(clicon_handle h, char *stream, stream_fn_t fn); +int stream_cb_add(clicon_handle h, char *stream, char *xpath, 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); +#if defined(__GNUC__) && __GNUC__ >= 3 +int stream_notify(clicon_handle h, char *stream, const char *event, ...) __attribute__ ((format (printf, 3, 4))); +#else +int stream_notify(clicon_handle h, char *stream, const char *event, ...); +#endif #endif /* _CLIXON_STREAM_H_ */ diff --git a/lib/src/clixon_handle.c b/lib/src/clixon_handle.c index 31158ddc..940d7cf6 100644 --- a/lib/src/clixon_handle.c +++ b/lib/src/clixon_handle.c @@ -136,7 +136,7 @@ clicon_handle_exit(clicon_handle h) hash_free(copt); if ((data = clicon_data(h)) != NULL) hash_free(data); - stream_free(clicon_stream(h)); + stream_delete_all(clicon_stream(h)); free(ch); return 0; } diff --git a/lib/src/clixon_log.c b/lib/src/clixon_log.c index 9e8e2f1a..87e0c8de 100644 --- a/lib/src/clixon_log.c +++ b/lib/src/clixon_log.c @@ -52,6 +52,9 @@ #include #include +/* cligen */ +#include + /* clicon */ #include "clixon_err.h" #include "clixon_log.h" @@ -62,10 +65,6 @@ int debug = 0; /* Bitmask whether to log to syslog or stderr: CLICON_LOG_STDERR | CLICON_LOG_SYSLOG */ static int _logflags = 0x0; -/* Function pointer to log notify callback */ -static clicon_log_notify_t *_log_notify_cb = NULL; -static void *_log_notify_arg = NULL; - /* Set to open file to write debug messages directly to file */ static FILE *_logfile = NULL; @@ -81,15 +80,14 @@ static FILE *_logfile = NULL; * if CLICON_LOG_SYSLOG, then print logs to syslog * You can do a combination of both * @code - * clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR); + * clicon_log_init(h, __PROGRAM__, LOG_INFO, CLICON_LOG_STDERR); * @endcode */ int -clicon_log_init(char *ident, - int upto, - int flags) +clicon_log_init(char *ident, + int upto, + int flags) { - _logflags = flags; if (flags & CLICON_LOG_SYSLOG){ if (setlogmask(LOG_UPTO(upto)) < 0) @@ -160,18 +158,6 @@ clicon_get_logflags(void) return _logflags; } -/*! Register log callback, return old setting - */ -clicon_log_notify_t * -clicon_log_register_callback(clicon_log_notify_t *cb, - void *arg) -{ - clicon_log_notify_t *old = _log_notify_cb; - _log_notify_cb = cb; - _log_notify_arg = arg; - return old; -} - /*! Mimic syslog and print a time on file f */ static int @@ -188,6 +174,7 @@ flogtime(FILE *f) return 0; } +#ifdef NOTUSED /* * Mimic syslog and print a time on string s * String returned needs to be freed. @@ -211,19 +198,19 @@ slogtime(void) tm->tm_hour, tm->tm_min, tm->tm_sec); return str; } - +#endif /*! Make a logging call to syslog (or stderr). * * @param[in] level log level, eg LOG_DEBUG,LOG_INFO,...,LOG_EMERG. Thisis OR:d with facility == LOG_USER * @param[in] msg Message to print as argv. * This is the _only_ place the actual syslog (or stderr) logging is made in clicon,.. - * @note syslog makes itw own filtering, but if log to stderr we do it here + * @note syslog makes its own filtering, but if log to stderr we do it here * @see clicon_debug */ -int -clicon_log_str(int level, - char *msg) +static int +clicon_log_str(int level, + char *msg) { if (_logflags & CLICON_LOG_SYSLOG) syslog(LOG_MAKEPRI(LOG_USER, level), "%s", msg); @@ -245,30 +232,10 @@ clicon_log_str(int level, fprintf(_logfile, "%s\n", msg); fflush(_logfile); } - if (_log_notify_cb){ - static int cb = 0; - char *d, *msg2; - int len; - if (cb++ == 0){ - /* Here there is danger of recursion: if callback in turn logs, therefore - make static check (should be stack-based - now global) - */ - if ((d = slogtime()) == NULL) - return -1; - len = strlen(d) + strlen(msg) + 1; - if ((msg2 = malloc(len)) == NULL){ - fprintf(stderr, "%s: malloc: %s\n", __FUNCTION__, strerror(errno)); - return -1; - } - snprintf(msg2, len, "%s%s", d, msg); - assert(_log_notify_arg); - _log_notify_cb(level, msg2, _log_notify_arg); - free(d); - free(msg2); - } - cb--; - } + /* Enable this if you want syslog in a stream. But there are problems with + * recursion + */ done: return 0; } diff --git a/lib/src/clixon_proto.c b/lib/src/clixon_proto.c index 1eaa4a66..17b09614 100644 --- a/lib/src/clixon_proto.c +++ b/lib/src/clixon_proto.c @@ -64,10 +64,10 @@ /* clicon */ #include "clixon_err.h" -#include "clixon_log.h" #include "clixon_queue.h" #include "clixon_hash.h" #include "clixon_handle.h" +#include "clixon_log.h" #include "clixon_yang.h" #include "clixon_sig.h" #include "clixon_xml.h" @@ -564,16 +564,16 @@ send_msg_reply(int s, * @param[in] event * @retval 0 OK * @retval -1 Error + * @see send_msg_notify_xml */ -int +static int send_msg_notify(int s, - int level, char *event) { int retval = -1; struct clicon_msg *msg = NULL; - if ((msg=clicon_msg_encode("%s", event)) == NULL) + if ((msg=clicon_msg_encode("%s", event)) == NULL) goto done; if (clicon_msg_send(s, msg) < 0) goto done; @@ -584,6 +584,37 @@ send_msg_notify(int s, return retval; } +/*! Send a clicon_msg NOTIFY message asynchronously to client + * + * @param[in] s Socket to communicate with client + * @param[in] level + * @param[in] xml Event as XML + * @retval 0 OK + * @retval -1 Error + * @see send_msg_notify XXX beauty contest + */ +int +send_msg_notify_xml(int s, + cxobj *xev) +{ + int retval = -1; + cbuf *cb = NULL; + + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_PLUGIN, errno, "cbuf_new"); + goto done; + } + if (clicon_xml2cbuf(cb, xev, 0, 0) < 0) + goto done; + if (send_msg_notify(s, cbuf_get(cb)) < 0) + goto done; + retval = 0; + done: + if (cb) + cbuf_free(cb); + return retval; +} + /*! Look for a text pattern in an input string, one char at a time * @param[in] tag What to look for * @param[in] ch New input character diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 63c4e4cf..f5246109 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -57,9 +57,9 @@ /* clicon */ #include "clixon_queue.h" -#include "clixon_log.h" #include "clixon_hash.h" #include "clixon_handle.h" +#include "clixon_log.h" #include "clixon_yang.h" #include "clixon_options.h" #include "clixon_xml.h" @@ -775,7 +775,7 @@ clicon_rpc_create_subscription(clicon_handle h, username = clicon_username_get(h); if ((msg = clicon_msg_encode("" "%s" - "%s" + "" "", username?username:"", stream?stream:"", filter?filter:"")) == NULL) diff --git a/lib/src/clixon_stream.c b/lib/src/clixon_stream.c index 18eb6d50..25538b2f 100644 --- a/lib/src/clixon_stream.c +++ b/lib/src/clixon_stream.c @@ -44,6 +44,7 @@ #include #include #include +#include /* cligen */ #include @@ -51,8 +52,14 @@ /* clicon */ #include "clixon_queue.h" #include "clixon_err.h" +#include "clixon_string.h" #include "clixon_hash.h" #include "clixon_handle.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_options.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" #include "clixon_stream.h" /*! Find an event notification stream given name @@ -74,6 +81,7 @@ stream_find(clicon_handle h, } /*! Add notification event stream + * */ int stream_register(clicon_handle h, @@ -105,10 +113,10 @@ stream_register(clicon_handle h, return retval; } -/*! Delete complete notification event stream list +/*! Delete complete notification event stream list (not just single stream) */ int -stream_free(event_stream_t *es) +stream_delete_all(event_stream_t *es) { event_stream_t *e_next; @@ -148,8 +156,11 @@ stream_get_xml(clicon_handle h, if (access){ cprintf(cb, ""); cprintf(cb, "xml"); - /* Note /stream need to be in http proxy declaration */ - cprintf(cb, "https://example.com/stream/%s", es->es_name); + /* Note /stream need to be in http proxy declaration + * XXX + */ + cprintf(cb, "/stream/%s", es->es_name); + cprintf(cb, ""); } cprintf(cb, ""); @@ -173,6 +184,7 @@ stream_del() /*! Add an event notification callback to a stream given a callback function * @param[in] h Clicon handle * @param[in] stream Name of stream + * @param[in] xpath Filter selector - xpath * @param[in] fn Callback when event occurs * @param[in] arg Argument to use with callback. Also handle when deleting * @retval 0 OK @@ -182,6 +194,7 @@ stream_del() int stream_cb_add(clicon_handle h, char *stream, + char *xpath, stream_fn_t fn, void *arg) { @@ -190,7 +203,7 @@ stream_cb_add(clicon_handle h, struct stream_subscription *ss; if ((es = stream_find(h, stream)) == NULL){ - clicon_err(OE_CFG, ENOENT, "Stream not found"); + clicon_err(OE_CFG, ENOENT, "Stream %s not found", stream); goto done; } if ((ss = malloc(sizeof(*ss))) == NULL){ @@ -198,7 +211,14 @@ stream_cb_add(clicon_handle h, goto done; } memset(ss, 0, sizeof(*ss)); - ss->ss_stream = strdup(stream); + if ((ss->ss_stream = strdup(stream)) == NULL){ + clicon_err(OE_CFG, errno, "strdup"); + goto done; + } + if (xpath && (ss->ss_xpath = strdup(xpath)) == NULL){ + clicon_err(OE_CFG, errno, "strdup"); + goto done; + } ss->ss_fn = fn; ss->ss_arg = arg; ss->ss_next = es->es_subscription; @@ -208,34 +228,149 @@ stream_cb_add(clicon_handle h, return retval; } -/*! Delete event notification callback to a stream given a callback function - * Alt just send in an ss struct? +/*! Delete event notification callback to a stream given a callback and arg + * @param[in] h Clicon handle + * @param[in] stream Name of stream or NULL for all streams + * @param[in] fn Callback when event occurs + * @param[in] arg Argument to use with callback. Also handle when deleting + * @retval 0 OK + * @retval -1 Error */ int stream_cb_delete(clicon_handle h, char *stream, - stream_fn_t fn) + stream_fn_t fn, + void *arg) { int retval = -1; event_stream_t *es; - struct stream_subscription *ss; struct stream_subscription **ss_prev; - - if ((es = stream_find(h, stream)) == NULL) - goto ok; - ss_prev = &es->es_subscription; - for (ss = *ss_prev; ss; ss = ss->ss_next){ - if (ss->ss_fn == fn){ - *ss_prev = ss->ss_next; - free(ss->ss_stream); - if (ss->ss_arg) - free(ss->ss_arg); - free(ss); - break; + struct stream_subscription *ss; + 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; + for (ss = *ss_prev; ss; ss = ss_next){ + ss_next = ss->ss_next; + if (fn == ss->ss_fn && arg == ss->ss_arg){ + *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; } - ss_prev = &ss->ss_next; } - ok: retval = 0; return retval; } + +/*! Stream notify event and distribute to all registered callbacks + * @param[in] h Clicon handle + * @param[in] stream Name of event stream. CLICON is predefined as LOG stream + * @param[in] event Notification as xml tree + * @retval 0 OK + * @retval -1 Error with clicon_err called + * @see stream_notify + */ +int +stream_notify_xml(clicon_handle h, + char *stream, + cxobj *xevent) +{ + int retval = -1; + event_stream_t *es; + struct stream_subscription *ss; + + if ((es = stream_find(h, stream)) == NULL) + goto ok; + /* Go thru all global (handle) subscriptions and find matches */ + for (ss = es->es_subscription; ss; ss = ss->ss_next){ + 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; + } + ok: + retval = 0; + done: + return retval; +} + +/*! Stream notify event and distribute to all registered callbacks + * @param[in] h Clicon handle + * @param[in] stream Name of event stream. CLICON is predefined as LOG stream + * @param[in] event Notification as format string according to printf(3) + * @retval 0 OK + * @retval -1 Error with clicon_err called + * @code + * if (stream_notify(h, "NETCONF", "faultEthernet0major") < 0) + * err; + * @endcode + * @see stream_notify_xml + * @see backend_notify + */ +int +stream_notify(clicon_handle h, + char *stream, + const char *event, ...) +{ + int retval = -1; + va_list args; + int len; + cxobj *xev = NULL; + yang_spec *yspec = NULL; + char *str = NULL; + cbuf *cb = NULL; + char timestr[27]; + struct timeval tv; + + va_start(args, event); + len = vsnprintf(NULL, 0, event, args) + 1; + va_end(args); + if ((str = malloc(len)) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(str, 0, len); + va_start(args, event); + len = vsnprintf(str, len, event, args) + 1; + va_end(args); + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_YANG, 0, "No yang spec"); + goto done; + } + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + + gettimeofday(&tv, NULL); + if (time2str(tv, timestr, sizeof(timestr)) < 0){ + clicon_err(OE_UNIX, errno, "time2str"); + goto done; + } + cprintf(cb, "%s%s", timestr, str); + if (xml_parse_string(cbuf_get(cb), yspec, &xev) < 0) + goto done; + if (xml_rootchild(xev, 0, &xev) < 0) + goto done; + if (stream_notify_xml(h, stream, xev) < 0) + goto done; + retval = 0; + done: + if (cb) + cbuf_free(cb); + if (xev) + xml_free(xev); + if (str) + free(str); + return retval; +} diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index c2393782..451d6128 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -55,11 +55,11 @@ /* clixon */ #include "clixon_err.h" -#include "clixon_log.h" #include "clixon_string.h" #include "clixon_queue.h" #include "clixon_hash.h" #include "clixon_handle.h" +#include "clixon_log.h" #include "clixon_yang.h" #include "clixon_xml.h" #include "clixon_xml_sort.h" @@ -1412,7 +1412,8 @@ xml_parse_file(int fd, * cxobj *xt = NULL; * if (xml_parse_string(str, yspec, &xt) < 0) * err; - * xml_free(xt); + * if (xml_root_child(xt, 0, &xt) < 0) # If you want to remove TOP + * err; * @endcode * @see xml_parse_file * @see xml_parse_va diff --git a/test/lib.sh b/test/lib.sh index 2e7d0549..a5d0da09 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -2,6 +2,8 @@ # Define test functions. # Create working dir as variable "dir" +#set -e + testnr=0 testname= @@ -32,6 +34,7 @@ err(){ echo -e "\e[31m\nError in Test$testnr [$testname]:" if [ $# -gt 0 ]; then echo "Expected: $1" + echo fi if [ $# -gt 1 ]; then echo "Received: $2" @@ -172,15 +175,27 @@ expectwait(){ input=$2 expect=$3 wait=$4 - + # Do while read stuff - sleep 10|cat <(echo $input) -| $cmd | while [ 1 ] ; do - read ret + echo timeout > /tmp/flag + ret="" + sleep $wait | cat <(echo $input) -| $cmd | while [ 1 ] ; do + read r +# echo "r:$r" + ret="$ret$r" match=$(echo "$ret" | grep -Eo "$expect"); if [ -z "$match" ]; then + echo error > /tmp/flag err $expect "$ret" + else + echo ok > /tmp/flag # only this is OK + break; fi - break done + cat /tmp/flag + if [ $(cat /tmp/flag) != "ok" ]; then + cat /tmp/flag + exit + fi } diff --git a/test/test_auth_ext.sh b/test/test_auth_ext.sh index 55d57739..0d5d9dda 100755 --- a/test/test_auth_ext.sh +++ b/test/test_auth_ext.sh @@ -39,12 +39,6 @@ EOF cat < $fyang module $APPNAME{ prefix ex; - import ietf-interfaces { - prefix if; - } - import iana-if-type { - prefix ianaift; - } container authentication { description "Example code for enabling www basic auth and some example users"; @@ -70,21 +64,13 @@ module $APPNAME{ type int32; description "something to edit"; } - container interfaces-state { + container state { config false; - list interface{ - key "name"; - leaf name{ - type string; - } - leaf type{ - type string; - } - leaf if-index { - type int32; - } + description "state data for example application"; + leaf-list op { + type string; + } } - } } EOF @@ -191,7 +177,7 @@ new "restconf DELETE whole datastore" expecteq "$(curl -u adm1:bar -sS -X DELETE http://localhost/restconf/data)" "" new2 "auth get" -expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/ietf-interfaces:interfaces-state)" '{"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}} +expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/state)" '{"state": {"op": "42"}} ' new "Set x to 0" diff --git a/test/test_event.sh b/test/test_event.sh index de7c0689..65cdbc4c 100755 --- a/test/test_event.sh +++ b/test/test_event.sh @@ -16,6 +16,8 @@ cat < $cfg $fyang false /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/lib/$APPNAME/backend + example_backend.so$ $dir/restconf.pidfile /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so @@ -28,12 +30,12 @@ cat < $cfg EOF -# RFC5277 NETCONF Event Notifications +# RFC5277 NETCONF Event Notifications +# using reportingEntity (rfc5277) not reporting-entity (rfc8040) cat < $fyang module example { namespace "http://example.com/event/1.0"; prefix ex; - organization "Example, Inc."; contact "support at example.com"; description "Example Notification Data Model Module."; @@ -41,14 +43,13 @@ cat < $fyang description "Initial version."; reference "example.com document 2-9976."; } - notification event { description "Example notification event."; leaf event-class { type string; description "Event class identifier."; } - container reporting-entity { + container reportingEntity { description "Event specific information."; leaf card { type string; @@ -60,7 +61,14 @@ cat < $fyang description "Event severity description."; } } - } + container state { + config false; + description "state data for example application"; + leaf-list op { + type string; + } + } + } EOF # kill old backend (if any) @@ -78,30 +86,35 @@ fi new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf - + new "start restconf daemon" sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -D 1 sleep 1 -# get the stream list using netconf new "netconf event stream discovery RFC5277 Sec 3.2.5" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'CLICONClicon logsfalseNETCONFdefault NETCONF event streamfalse]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'NETCONFdefault NETCONF event streamfalse]]>]]>' new "netconf event stream discovery RFC8040 Sec 6.2" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'CLICONClicon logsfalsexmlhttps://example.com/stream/CLICONNETCONFdefault NETCONF event streamfalsexmlhttps://example.com/stream/NETCONF]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'NETCONFdefault NETCONF event streamfalsexml/stream/NETCONF]]>]]>' 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": "CLICON","description": "Clicon logs","replay-support": false,"access": \[{"encoding": "xml","location": "https://example.com/stream/CLICON"}\]},{ "name": "NETCONF","description": "default NETCONF event stream","replay-support": false,"access": \[{"encoding": "xml","location": "https://example.com/stream/NETCONF"}\]}\]}' +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"}\]}\]}' 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": "https://example.com/stream/NETCONF"}' +expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams/stream=NETCONF/access=xml/location" 0 '{"location": "/stream/NETCONF"}' -new "restconf monitor event stream RFC8040 Sec 6.3" -#expectfn "curl -s -X GET http://localhost/stream/NETCONF" 0 '' +new "netconf NETCONF subscription" +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'NETCONF]]>]]>' '^]]>]]>20' 5 -#new "netconf subscription" NOTYET -#expectwait "$clixon_netconf -qf $cfg -y $fyang" "ROUTING]]>]]>" "^]]>]]>Routing notification]]>]]>$" 30 +new "netconf NETCONF subscription with simple filter" +expectwait "$clixon_netconf -qf $cfg -y $fyang" "NETCONF]]>]]>" '^]]>]]>20' 5 + +new "netconf NETCONF subscription with filter classfier" +expectwait "$clixon_netconf -qf $cfg -y $fyang" "NETCONF]]>]]>" '^]]>]]>20' 5 + +#new "restconf monitor event stream RFC8040 Sec 6.3" +#XXX expectfn "curl -s -X GET http://localhost/stream/NETCONF" 0 '' new "Kill restconf daemon" sudo pkill -u www-data clixon_restconf @@ -119,4 +132,4 @@ if [ -n "$pid" ]; then sudo kill $pid fi -rm -rf $dir +#rm -rf $dir diff --git a/test/test_netconf.sh b/test/test_netconf.sh index 135a865e..f662705c 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -65,6 +65,13 @@ module example{ } } } + container state { + config false; + description "state data for example application"; + leaf-list op { + type string; + } + } } EOF @@ -180,10 +187,10 @@ new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf edit state operation should fail" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth1ex:eth]]>]]>" "^invalid-value" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "42]]>]]>" "^invalid-value" new "netconf get state operation" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^eth0ex:eth42]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^42]]>]]>$" new "netconf lock/unlock" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>]]>]]>" "^]]>]]>]]>]]>$" @@ -225,18 +232,19 @@ new "netconf client-side rpc" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "example]]>]]>" "^ok]]>]]>$" new "netconf subscription" -expectwait "$clixon_netconf -qf $cfg -y $fyang" "ROUTING]]>]]>" "^]]>]]>Routing notification]]>]]>$" 30 +expectwait "$clixon_netconf -qf $cfg -y $fyang" "NETCONF]]>]]>" '^]]>]]>201' 10 new "Kill backend" -# Check if still alive -pid=`pgrep clixon_backend` -if [ -z "$pid" ]; then - err "backend already dead" -fi # kill backend sudo clixon_backend -zf $cfg if [ $? -ne 0 ]; then err "kill backend" fi +# Check if still alive +pid=`pgrep clixon_backend` +if [ -n "$pid" ]; then + sudo kill $pid +fi + rm -rf $dir diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 3e10acd6..4ca2aa76 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -71,11 +71,18 @@ module example{ } } } + container state { + config false; + description "state data for example application"; + leaf-list op { + type string; + } + } } EOF # This is a fixed 'state' implemented in routing_backend. It is assumed to be always there -state='{"interfaces-state": {"interface": \[{"name": "eth0","type": "ex:eth","if-index": 42}\]}}' +state='{"state": {"op": "42"}}' # kill old backend (if any) new "kill old backend" @@ -136,7 +143,7 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi -new "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895" +new2 "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895" expecteq "$(curl -s -H 'Accept: application/yang-data+json' -G http://localhost/restconf/data/ietf-yang-library:modules-state/module=ietf-routing,2014-10-26/)" '{"module": [{"name": "ietf-routing","revision": "2014-10-26","namespace": "urn:ietf:params:xml:ns:yang:ietf-routing"}]} ' @@ -151,52 +158,52 @@ new "restconf empty rpc" expecteq "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/example:empty)" "" new2 "restconf get empty config + state json" -expecteq "$(curl -sSG http://localhost/restconf/data/interfaces-state)" '{"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}} +expecteq "$(curl -sSG http://localhost/restconf/data/state)" '{"state": {"op": "42"}} ' -new2 "restconf get empty config + state json with module name" -expecteq "$(curl -sSG http://localhost/restconf/data/ietf-interfaces:interfaces-state)" '{"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}} +new2 "restconf get empty config + state json + module" +expecteq "$(curl -sSG http://localhost/restconf/data/example:state)" '{"state": {"op": "42"}} ' new2 "restconf get empty config + state json with wrong module name" -expecteq "$(curl -sSG http://localhost/restconf/data/badmodule:interfaces-state)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "No yang node found: badmodule:interfaces-state"}}}} ' +expecteq "$(curl -sSG http://localhost/restconf/data/badmodule:state)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "No yang node found: badmodule:state"}}}} ' new "restconf get empty config + state xml" -ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/interfaces-state) -expect="eth0ex:eth42" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/state) +expect="42" match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi -new2 "restconf get data/interfaces-state/interface=eth0 json" -expecteq "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0)" '{"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]} +new2 "restconf get data/ json" +expecteq "$(curl -s -G http://localhost/restconf/data/state/op=42)" '{"op": "42"} ' new "restconf get state operation eth0 xml" # Cant get shell macros to work, inline matching from lib.sh -ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/interfaces-state/interface=eth0) -expect="eth0ex:eth42" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/state/op=42) +expect="42" match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new2 "restconf get state operation eth0 type json" -expecteq "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0/type)" '{"type": "ex:eth"} +expecteq "$(curl -s -G http://localhost/restconf/data/state/op=42)" '{"op": "42"} ' new "restconf get state operation eth0 type xml" # Cant get shell macros to work, inline matching from lib.sh -ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/interfaces-state/interface=eth0/type) -expect="ex:eth" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/state/op=42) +expect="42" match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new2 "restconf GET datastore" -expecteq "$(curl -s -X GET http://localhost/restconf/data/interfaces-state)" '{"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}} +expecteq "$(curl -s -X GET http://localhost/restconf/data/state)" '{"state": {"op": "42"}} ' # Exact match @@ -210,14 +217,14 @@ expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type" #expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"ex:eth\",\"enabled\":true}}} http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' new "restconf Check interfaces eth/0/0 added" -expectfn "curl -s -G http://localhost/restconf/data" 0 '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "ex:eth","enabled": true}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "ex:eth","if-index": 42}\]}} +expectfn "curl -s -G http://localhost/restconf/data" 0 '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "ex:eth","enabled": true}\]},"state": {"op": "42"}} ' new "restconf delete interfaces" expecteq $(curl -s -X DELETE http://localhost/restconf/data/interfaces) "" new "restconf Check empty config" -expectfn "curl -sG http://localhost/restconf/data/interfaces-state" 0 "$state" +expectfn "curl -sG http://localhost/restconf/data/state" 0 "$state" new "restconf Add interfaces subtree eth/0/0 using POST" expectfn 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}} http://localhost/restconf/data/interfaces' 0 "" @@ -229,7 +236,7 @@ expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces ' new2 "restconf Check eth/0/0 added state" -expecteq "$(curl -s -G http://localhost/restconf/data/interfaces-state)" '{"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}} +expecteq "$(curl -s -G http://localhost/restconf/data/state)" '{"state": {"op": "42"}} ' new2 "restconf Re-post eth/0/0 which should generate error"