diff --git a/.gitignore b/.gitignore
index 1b2cb35f..e9ed0e3e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,7 +8,6 @@ Makefile
apps/Makefile
apps/*/Makefile
docker/Makefile
-docker/*/Makefile
etc/Makefile
example/Makefile
lib/Makefile
@@ -37,8 +36,6 @@ docker/netconf/Dockerfile
etc/clixonrc
-example/*.conf
-
include/clixon_config.h
lib/src/build.c
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ff9c8650..b6793e1d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,109 @@
# Clixon Changelog
+## 3.8.0 (Expected: Nov 4)
+
+### Major New features
+* YANG Features
+ * Yang 1.1 feature and if-feature according to RFC 7950 7.20.1 and 7.20.2.
+ * See https://github.com/clicon/clixon/issues/41
+ * Features are declared via CLICON_FEATURE in the configuration file. Example below shows enabling (1) a specific feature; (2) all features in a module; (3) all features in all modules:
+ ```
+ ietf-routing:router-id
+ ietf-routing:*
+ *:*
+ ```
+ * logical combination of features not implemented, eg if-feature "not foo or bar and baz";
+ * ietf-netconf yang module added with candidate, validate, startup and xpath features enabled.
+* YANG module library
+ * YANG modules according to RFC 7895 and implemented by ietf-yang-library.yang
+ * Enabled by configuration option CLICON_MODULE_LIBRARY_RFC7895 - enabled by default
+ * RFC 7895 defines a module-set-id. Configure option CLICON_MODULE_SET_ID is set and changed when modules change.
+* Yang 1.1 notification support (RFC 7950: Sec 7.16)
+* New event streams implementation with replay
+ * See clicon_stream.[ch] for details
+ * Added stream discovery according to RFC 5277 for netconf and RFC 8040 for restconf
+ * Enabled by CLICON_STREAM_DISCOVERY_RFC5277 and CLICON_STREAM_DISCOVERY_RFC8040
+ * Configure option CLICON_STREAM_RETENTION is default number of seconds before dropping replay buffers
+8040.
+* Restconf stream notification support according to RFC8040
+ * See (apps/restconf/README.md) for more details.
+ * start-time and stop-time query parameters
+ * Fork fcgi handler for streams
+ * Set access/subscribe base URL with: CLICON_STREAM_URL (default "https://localhost") and CLICON_STREAM_PATH (default "streams")
+ * Example: new stream "foo" will get access URL: https://localhost/streams/foo
+ * Alternative variant using pub/sub support enabled by ./configure --enable-publish
+ * Set publish URL base with: CLICON_STREAM_PUB (default http://localhost/pub)
+ * Example: new stream "foo" will publish event streams on URL: https://localhost/pub/foo
+ * 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 supported
+ * 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)
+* Netconf hello capability updated to YANG 1.1 RFC7950 Sec 5.6.4
+ * Added urn:ietf:params:netconf:capability:yang-library:1.0
+ * Thanks @SCadilhac for helping out, see https://github.com/clicon/clixon/issues/39
+* Major rewrite of event streams (as described above)
+ * 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 modified:
+ * clicon_log_register_callback() removed
+ * subscription_add() --> stream_add()
+ * stream_cb_add() --> stream_ss_add()
+ * stream_cb_delete() --> stream_ss_delete()
+ * backend_notify() and backend_notify_xml() - use stream_notify() instead
+ * Example uses "NETCONF" stream instead of "ROUTING"
+* clixon_restconf and clixon_netconf changed to take -D `` as command-line option instead of just -D (without debig level)
+ * This aligns to clixon_cli and clixon_backend
+* Application command option -S to clixon_netconf is obsolete. Use `clixon_netconf -l s` instead.
+* Unified log handling for all clicon applications using command-line option: `-l e|o|s|f`.
+ * The options stand for e:stderr, o:stdout, s: syslog, f:file
+ * Added file logging (`-l f` or `-l f`) for cases where neither syslog nor stderr is useful.
+* 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/foobar:modules-state
+* Generilized top-level yang parsing functions
+ * Clarified semantics of main yang module:
+ * Command-line option -y MUST specify a filename
+ * Configure option CLICON_YANG_MODULE_MAIN MUST specify a module name
+ * yang_parse() changed to take either filename or module name and revision.
+ * Removed clicon_dbspec_name() and clicon_dbspec_name_set().
+ * Replace calls to yang_spec_main() with yang_spec_parse_module(). See for
+ example backend_main() and others if you need details.
+
+### Minor changes
+* Renamed test/test_auth*.sh tests to test/test_nacm*.sh
+* YANG keywords "action" and "belongs-to" implemented by syntactically by parser (but not proper semantics).
+* clixon-config YAML file has new revision: 2018-10-21.
+* Allow new lines in CLI prompts
+* uri_percent_encode() and xml_chardata_encode() changed to use stdarg parameters
+* Added Configure option CLIXON_DEFAULT_CONFIG=/usr/local/etc/clixon.xml as option and in example (so you dont need to provide -f command-line option).
+* New function: clicon_conf_xml() returns configuration tree
+* Obsoleted COMPAT_CLIV and COMPAT_XSL that were optional in 3.7
+* Added command-line option `-t ` for clixon_netconf - quit after max time.
+
+### Corrected Bugs
+* No space after ampersand escaped characters in XML https://github.com/clicon/clixon/issues/52
+ * Thanks @SCadilhac
+* Single quotes for XML attributes https://github.com/clicon/clixon/issues/51
+ * Thanks @SCadilhac
+* Fixed https://github.com/clicon/clixon/issues/46 Issue with empty values in leaf-list
+ * Thanks achernavin22
+* Identity without any identityref:s caused SEGV
+* Memory error in backend transaction revert
+* Set dir /www-data with www-data as owner, see https://github.com/clicon/clixon/issues/37
+
+### Known issues
+* 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.
+* Namespace name relabeling is not supported.
+ * Eg: if "des" is defined as prefix for an imported module, then a relabeling using xmlns is not supported, such as:
+```
+ x:des3
+```
+
## 3.7.0 (22 July 2018)
### Major New features
diff --git a/Makefile.in b/Makefile.in
index 84368869..3d34933a 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -67,12 +67,12 @@ depend:
install:
for i in $(SUBDIRS) doc; \
- do (cd $$i; $(MAKE) $(MFLAGS) $@)||exit 1; done; \
+ do (cd $$i; $(MAKE) $(MFLAGS) $@)||exit 1; done;
echo "Install for compilation by: make install-include"
install-include:
for i in $(SUBDIRS) doc; \
- do (cd $$i && $(MAKE) $(MFLAGS) $@)||exit 1; done; \
+ do (cd $$i && $(MAKE) $(MFLAGS) $@)||exit 1; done;
echo "To install example app: cd example; make; make install"
uninstall:
diff --git a/README.md b/README.md
index cfbb6617..4feae79d 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
# Clixon
-Clixon is an automatic configuration manager where you generate
-interactive CLI, NETCONF, RESTCONF and embedded databases with
-transaction support from a YANG specification.
+Clixon is a YANG-based configuration manager, with interactive CLI,
+NETCONF and RESTCONF interfaces, an embedded database and transaction
+support.
* [Background](#background)
* [Frequently asked questions](doc/FAQ.md)
@@ -16,7 +16,8 @@ transaction support from a YANG specification.
* [Netconf](#netconf)
* [Restconf](#restconf)
* [Datastore](datastore/README.md)
- * [Authentication and Authorization](#auth)
+ * [Authentication](#auth)
+ * [NACM Access control](#nacm)
* [Example](example/)
* [Changelog](CHANGELOG.md)
* [Runtime](#runtime)
@@ -107,24 +108,21 @@ specification for handling XML configuration data. The YANG spec is
used to generate an interactive CLI, netconf and restconf clients. It
also manages an XML datastore.
-Clixon mainly follows [YANG 1.0 RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) with some exceptions:
-- conformance: feature, if-feature, deviation
-- list features: min/max-elements, unique
-- action statements
-- notifications
+Clixon follows:
+- [YANG 1.0 RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt)
+- [YANG 1.1 RFC 7950](https://www.rfc-editor.org/rfc/rfc7950.txt).
+- [RFC 7895: YANG module library](http://www.rfc-base.org/txt/rfc-7895.txt)
-The aim is also to cover new features in YANG 1.1 [YANG RFC 7950](https://www.rfc-editor.org/rfc/rfc7950.txt)
-
-Clixon has its own XML library designed for performance.
+However, the following YANG syntax modules are not implemented:
+`deviation`, `min/max-elements`, `unique`, and `action`.
Netconf
=======
Clixon implements the following NETCONF proposals or standards:
-- [NETCONF Configuration Protocol](http://www.rfc-base.org/txt/rfc-4741.txt)
-- [Using the NETCONF Configuration Protocol over Secure Shell (SSH)](http://www.rfc-base.org/txt/rfc-4742.txt)
-- [NETCONF Event Notifications](http://www.rfc-base.org/txt/rfc-5277.txt)
-
-Some updates are being made to RFC 6241 and RFC 6242.
+- [RFC 6241: NETCONF Configuration Protocol](http://www.rfc-base.org/txt/rfc-6241.txt)
+- [RFC 6242: Using the NETCONF Configuration Protocol over Secure Shell (SSH)](http://www.rfc-base.org/txt/rfc-6242.txt)
+- [RFC 5277: NETCONF Event Notifications](http://www.rfc-base.org/txt/rfc-5277.txt)
+- [RFC 8341: Network Configuration Access Control Model](http://www.rfc-base.org/txt/rfc-8341.txt)
Clixon does not yet support the following netconf features:
@@ -136,16 +134,18 @@ Clixon does not yet support the following netconf features:
Restconf
========
-Clixon restconf is a daemon based on FASTCGI. Instructions are available to
+Clixon Restconf is a daemon based on FastCGI C-API. Instructions are available to
run with NGINX.
The implementatation is based on [RFC 8040: RESTCONF Protocol](https://tools.ietf.org/html/rfc8040).
+
The following features are supported:
- OPTIONS, HEAD, GET, POST, PUT, DELETE
-The following are not implemented
+- stream notifications (RFC8040 sec 6)
+- query parameters start-time and stop-time(RFC8040 section 4.9)
+
+The following features are not implemented:
- PATCH
-- query parameters (section 4.9)
-- notifications (sec 6)
-- schema resource
+- query parameters other than start/stop-time.
See [more detailed instructions](apps/restconf/README.md).
@@ -155,8 +155,6 @@ The Clixon datastore is a stand-alone XML based datastore. The idea is
to be able to use different datastores backends with the same
API.
-Update: There used to be a key-value plugin based on qdbm but isnow obsoleted. Only a text datastore is implemented.
-
The datastore is primarily designed to be used by Clixon but can be used
separately.
@@ -164,7 +162,6 @@ See [more detailed instructions](datastore/README.md).
Auth
====
-
Authentication is managed outside Clixon using SSH, SSL, Oauth2, etc.
For CLI, login is typically made via SSH. For netconf, SSH netconf
@@ -179,10 +176,37 @@ The clients send the ID of the user using a "username" attribute with
the RPC calls to the backend. Note that the backend trusts the clients
so the clients can in principle fake a username.
-There is an ongoing effort to implement authorization for Clixon
-according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341), at
-least a subset of the functionality. See more information here:
-[NACM](README_NACM.md).
+NACM
+====
+Clixon includes an experimental Network Configuration Access Control Model (NACM) according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341). It has limited functionality.
+
+The support is as follows:
+
+* There is a yang config variable `CLICON_NACM_MODE` to set whether NACM is disabled, uses internal(embedded) NACM configuration, or external configuration. (See yang/clixon-config.yang)
+* If the mode is internal, NACM configurations is expected to be in the regular configuration, managed by regular candidate/runing/commit procedures. This mode may have some problems with bootstrapping.
+* If the mode is `external`, the `CLICON_NACM_FILE` yang config variable contains the name of a separate configuration file containing the NACM configurations. After changes in this file, the backend needs to be restarted.
+* The [example](example/README.md) contains a http basic auth and a NACM backend callback for mandatory state variables.
+* There are two [tests](test/README.md) using internal and external NACM config
+* The backend provides a limited NACM support (when enabled) described below
+
+NACM is implemented in the backend and a single access check is made
+in `from_client_msg()` when an internal netconf RPC has
+just been received and decoded. The code is in `nacm_access()`.
+
+The functionality is as follows:
+* Notification is not supported
+* Groups are supported
+* Rule-lists are supported
+* Rules are supported as follows
+ * module-name: Only '*' supported
+ * access-operations: only '*' and 'exec' supported
+ * rpc-name: fully supported (eg edit-config/get-config, etc)
+ * action: fully supported (permit/deny)
+
+The tests outlines an example of three groups (taken from the RFC): admin, limited and guest:
+* admin: Full access
+* limited: Read access (get and get-config)
+* guest: No access
Runtime
diff --git a/README_NACM.md b/README_NACM.md
deleted file mode 100644
index 7ec76ee3..00000000
--- a/README_NACM.md
+++ /dev/null
@@ -1,34 +0,0 @@
-# NACM
-
-Clixon includes an experimental NACM implementation according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341).
-
-The support is as follows:
-
-* There is a yang config variable `CLICON_NACM_MODE` to set whether NACM is disabled, uses internal(embedded) NACM configuration, or external configuration. (See yang/clixon-config.yang)
-* If the mode is internal, NACM configurations is expected to be in the regular configuration, managed by regular candidate/runing/commit procedures. This mode may have some problems with bootstrapping.
-* If the mode is `external`, the `CLICON_NACM_FILE` yang config variable contains the name of a separate configuration file containing the NACM configurations. After changes in this file, the backend needs to be restarted.
-* The [example](example/README.md) contains a http basic auth and a NACM backend callback for mandatory state variables.
-* There are two [tests](test/README.md) using internal and external NACM config
-* The backend provides a limited NACM support (when enabled) described below
-
-NACM functionality
-==================
-
-NACM is implemented in the backend and a single access check is made
-in from_client_msg() when an internal netconf RPC has
-just been received and decoded. The code is in nacm_access().
-
-The functionality is as follows:
-* Notification is not supported
-* Groups are supported
-* Rule-lists are supported
-* Rules are supported as follows
- * module-name: Only '*' supported
- * access-operations: only '*' and 'exec' supported
- * rpc-name: fully supported (eg edit-config/get-config, etc)
- * action: fully supported (permit/deny)
-
-The tests outlines an example of three groups (taken from the RFC): admin, limited and guest:
-* admin: Full access
-* limited: Read access (get and get-config)
-* guest: No access
diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c
index 7ffdbf2b..fedad77a 100644
--- a/apps/backend/backend_client.c
+++ b/apps/backend/backend_client.c
@@ -69,38 +69,9 @@
#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;
-}
-
static struct client_entry *
-ce_find_bypid(struct client_entry *ce_list, int pid)
+ce_find_bypid(struct client_entry *ce_list,
+ int pid)
{
struct client_entry *ce;
@@ -110,47 +81,45 @@ ce_find_bypid(struct client_entry *ce_list, int pid)
return NULL;
}
-static int
-client_subscription_delete(struct client_entry *ce,
- struct client_subscription *su0)
+/*! Stream callback for netconf stream notification (RFC 5277)
+ * @param[in] h Clicon handle
+ * @param[in] op 0:event, 1:rm
+ * @param[in] event Event as XML
+ * @param[in] arg Extra argument provided in stream_ss_add
+ * @see stream_ss_add
+ */
+int
+ce_event_cb(clicon_handle h,
+ int op,
+ cxobj *event,
+ void *arg)
{
- 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);
+ struct client_entry *ce = (struct client_entry *)arg;
+
+ clicon_debug(1, "%s op:%d", __FUNCTION__, op);
+ switch (op){
+ case 1:
+ /* Risk of recursion here */
+ if (ce->ce_s)
+ backend_client_rm(h, ce);
+ break;
+ default:
+ if (send_msg_notify_xml(ce->ce_s, event) < 0){
+ if (errno == ECONNRESET || errno == EPIPE){
+ clicon_log(LOG_WARNING, "client %d reset", ce->ce_nr);
+ }
break;
}
- su_prev = &su->su_next;
}
+ clicon_debug(1, "%s retval:0", __FUNCTION__);
return 0;
}
-#ifdef notused /* xxx */
-static struct client_subscription *
-client_subscription_find(struct client_entry *ce, char *stream)
-{
- struct client_subscription *su = NULL;
-
- for (su = ce->ce_subscription; su; su = su->su_next)
- if (strcmp(su->su_stream, stream) == 0)
- break;
-
- return su;
-}
-#endif
-
/*! Remove client entry state
* Close down everything wrt clients (eg sockets, subscriptions)
* Finally actually remove client struct in handle
* @param[in] h Clicon handle
- * @param[in] ce Client hadnle
+ * @param[in] ce Client handle
* @see backend_client_delete for actual deallocation of client entry struct
*/
int
@@ -160,8 +129,10 @@ backend_client_rm(clicon_handle h,
struct client_entry *c;
struct client_entry *c0;
struct client_entry **ce_prev;
- struct client_subscription *su;
+ clicon_debug(1, "%s", __FUNCTION__);
+ /* for all streams: XXX better to do it top-level? */
+ stream_ss_delete_all(h, ce_event_cb, (void*)ce);
c0 = backend_client_list(h);
ce_prev = &c0; /* this points to stack and is not real backpointer */
for (c = *ce_prev; c; c = c->ce_next){
@@ -171,12 +142,11 @@ 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);
break;
}
ce_prev = &c->ce_next;
}
+
return backend_client_delete(h, ce); /* actually purge it */
}
@@ -258,6 +228,124 @@ from_client_get_config(clicon_handle h,
return retval;
}
+/*! Get streams state according to RFC 8040 or RFC5277 common function
+ * @param[in] h Clicon handle
+ * @param[in] yspec Yang spec
+ * @param[in] xpath Xpath selection, not used but may be to filter early
+ * @param[in] module Name of yang module
+ * @param[in] top Top symbol, ie netconf or restconf-state
+ * @param[in,out] xret Existing XML tree, merge x into this
+ * @retval -1 Error (fatal)
+ * @retval 0 OK
+ * @retval 1 Statedata callback failed
+ */
+static int
+client_get_streams(clicon_handle h,
+ yang_spec *yspec,
+ char *xpath,
+ char *module,
+ char *top,
+ cxobj **xret)
+{
+ int retval = -1;
+ yang_stmt *ystream = NULL; /* yang stream module */
+ yang_stmt *yns = NULL; /* yang namespace */
+ cxobj *x = NULL;
+ cbuf *cb = NULL;
+
+ if ((ystream = yang_find((yang_node*)yspec, Y_MODULE, module)) == NULL){
+ clicon_err(OE_YANG, 0, "%s yang module not found", module);
+ goto done;
+ }
+ if ((yns = yang_find((yang_node*)ystream, Y_NAMESPACE, NULL)) == NULL){
+ clicon_err(OE_YANG, 0, "%s yang namespace not found", module);
+ goto done;
+ }
+ if ((cb = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, 0, "clicon buffer");
+ goto done;
+ }
+ cprintf(cb,"<%s xmlns=\"%s\">", top, yns->ys_argument);
+ if (stream_get_xml(h, strcmp(top,"restconf-state")==0, cb) < 0)
+ goto done;
+ cprintf(cb,"%s>", top);
+
+ if (xml_parse_string(cbuf_get(cb), yspec, &x) < 0){
+ if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0)
+ goto done;
+ retval = 1;
+ goto done;
+ }
+ retval = netconf_trymerge(x, yspec, xret);
+ done:
+ if (cb)
+ cbuf_free(cb);
+ if (x)
+ xml_free(x);
+ return retval;
+}
+
+
+/*! Get system state-data, including streams and plugins
+ * @param[in] h Clicon handle
+ * @param[in] xpath Xpath selection, not used but may be to filter early
+ * @param[in,out] xret Existing XML tree, merge x into this
+ * @retval -1 Error (fatal)
+ * @retval 0 OK
+ * @retval 1 Statedata callback failed
+ */
+static int
+client_statedata(clicon_handle h,
+ char *xpath,
+ cxobj **xret)
+{
+ int retval = -1;
+ cxobj **xvec = NULL;
+ size_t xlen;
+ int i;
+ yang_spec *yspec;
+
+ if ((yspec = clicon_dbspec_yang(h)) == NULL){
+ clicon_err(OE_YANG, ENOENT, "No yang spec");
+ goto done;
+ }
+ if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") &&
+ (retval = client_get_streams(h, yspec, xpath, "ietf-netconf-notification", "netconf", xret)) != 0)
+ goto done;
+ if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") &&
+ (retval = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) != 0)
+ goto done;
+ if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895") &&
+ (retval = yang_modules_state_get(h, yspec, xret)) != 0)
+ goto done;
+ if ((retval = clixon_plugin_statedata(h, yspec, xpath, xret)) != 0)
+ goto done;
+ /* Code complex to filter out anything that is outside of xpath */
+ if (xpath_vec(*xret, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
+ goto done;
+
+ /* If vectors are specified then mark the nodes found and
+ * then filter out everything else,
+ * otherwise return complete tree.
+ */
+ if (xvec != NULL){
+ for (i=0; i");
@@ -731,12 +819,13 @@ from_client_delete_config(clicon_handle h,
* @param[out] cbret Return xml value cligen buffer
* @retval 0 OK
* @retval -1 Error. Send error message back to client.
+ * @see RFC5277 2.1
* @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)
+ *
+ *
+ *
*
*/
static int
@@ -746,25 +835,66 @@ 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 *starttime = NULL;
+ char *stoptime = NULL;
+ char *selector = NULL;
+ struct timeval start;
+ struct timeval stop;
+
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 ((x = xpath_first(xe, "//stopTime")) != NULL){
+ if ((stoptime = xml_find_value(x, "body")) != NULL &&
+ str2time(stoptime, &stop) < 0){
+ if (netconf_bad_element(cbret, "application", "stopTime", "Expected timestamp") < 0)
+ goto done;
+ goto ok;
+ }
+ }
+ if ((x = xpath_first(xe, "//startTime")) != NULL){
+ if ((starttime = xml_find_value(x, "body")) != NULL &&
+ str2time(starttime, &start) < 0){
+ if (netconf_bad_element(cbret, "application", "startTime", "Expected timestamp") < 0)
+ goto done;
+ goto ok;
+ }
+ }
+ 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_find(h, stream)) == NULL){
+ if (netconf_invalid_value(cbret, "application", "No such stream") < 0)
+ goto done;
+ goto ok;
+ }
+ /* Add subscriber to stream - to make notifications for this client */
+ if (stream_ss_add(h, stream, selector,
+ starttime?&start:NULL, stoptime?&stop:NULL,
+ ce_event_cb, (void*)ce) < 0)
goto done;
+ /* Replay of this stream to specific subscription according to start and
+ * stop (if present).
+ * RFC 5277: If is not present, this is not a replay
+ * subscription.
+ * Schedule the replay to occur right after this RPC completes, eg "now"
+ */
+ if (starttime){
+ if (stream_replay_trigger(h, stream, ce_event_cb, (void*)ce) < 0)
+ goto done;
+ }
cprintf(cbret, "");
ok:
retval = 0;
@@ -987,13 +1117,7 @@ nacm_access(clicon_handle h,
if (username == NULL)
goto step10;
/* User's group */
- if (xpath_vec(xacm,
-#ifdef COMPAT_XSL
- "groups/group[user-name=%s]",
-#else
- "groups/group[user-name='%s']",
-#endif
- &gvec, &glen, username) < 0)
+ if (xpath_vec(xacm, "groups/group[user-name='%s']", &gvec, &glen, username) < 0)
goto done;
/* 5. If no groups are found, continue with step 10. */
if (glen == 0)
@@ -1010,13 +1134,7 @@ nacm_access(clicon_handle h,
for (j=0; j");
}
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 db331b7e..3856eb0e 100644
--- a/apps/backend/backend_main.c
+++ b/apps/backend/backend_main.c
@@ -73,19 +73,27 @@
#include "backend_handle.h"
/* Command line options to be passed to getopt(3) */
-#define BACKEND_OPTS "hD:f:d:b:Fzu:P:1s:c:g:y:x:" /* substitute s: for IRCc:r */
+#define BACKEND_OPTS "hD:f:l:d:b:Fza:u:P:1s:c:g:y:x:" /* substitute s: for IRCc:r */
+
+#define BACKEND_LOGFILE "/usr/local/var/clixon_backend.log"
/*! Terminate. Cannot use h after this */
static int
backend_terminate(clicon_handle h)
{
- yang_spec *yspec;
- char *pidfile = clicon_backend_pidfile(h);
- char *sockpath = clicon_sock(h);
+ yang_spec *yspec;
+ char *pidfile = clicon_backend_pidfile(h);
+ char *sockpath = clicon_sock(h);
+ cxobj *x;
clicon_debug(1, "%s", __FUNCTION__);
if ((yspec = clicon_dbspec_yang(h)) != NULL)
yspec_free(yspec);
+ if ((yspec = clicon_config_yang(h)) != NULL)
+ yspec_free(yspec);
+ if ((x = clicon_conf_xml(h)) != NULL)
+ xml_free(x);
+ stream_publish_exit();
clixon_plugin_exit(h);
/* Delete all backend plugin RPC callbacks */
rpc_callback_delete_all();
@@ -94,10 +102,10 @@ backend_terminate(clicon_handle h)
if (sockpath)
unlink(sockpath);
xmldb_plugin_unload(h); /* unload storage plugin */
- backend_handle_exit(h); /* Cannot use h after this */
+ backend_handle_exit(h); /* Also deletes streams. Cannot use h after this. */
event_exit();
- clicon_log_register_callback(NULL, NULL);
clicon_debug(1, "%s done", __FUNCTION__);
+ clicon_log_exit();
return 0;
}
@@ -125,23 +133,26 @@ usage(clicon_handle h,
char *confpid = clicon_backend_pidfile(h);
char *group = clicon_sock_group(h);
- fprintf(stderr, "usage:%s\n"
+ fprintf(stderr, "usage:%s *\n"
"where options are\n"
- " -h\t\tHelp\n"
- " -D \tDebug level\n"
- " -f \tCLICON config file (mandatory)\n"
- " -d \tSpecify backend plugin directory (default: %s)\n"
- " -b \tSpecify XMLDB database directory\n"
- " -z\t\tKill other config daemon and exit\n"
- " -F\t\tRun in foreground, do not run as daemon\n"
- " -1\t\tRun once and then quit (dont wait for events)\n"
- " -u \tConfig UNIX domain path / ip address (default: %s)\n"
- " -P \tPid filename (default: %s)\n"
- " -s \tSpecify backend startup mode: none|startup|running|init (replaces -IRCr\n"
- " -c \tLoad extra xml configuration, but don't commit.\n"
- " -g \tClient membership required to this group (default: %s)\n"
- " -y \tOverride yang spec file (dont include .yang suffix)\n"
- " -x \tXMLDB plugin\n",
+ "\t-h\t\tHelp\n"
+ "\t-D \tDebug level\n"
+ "\t-f \tCLICON config file\n"
+ "\t-l (s|e|o|f) Log on (s)yslog, std(e)rr or std(o)ut (stderr is default) Only valid if -F, if background syslog is on syslog.\n"
+ "\t-d \tSpecify backend plugin directory (default: %s)\n"
+ "\t-b \tSpecify XMLDB database directory\n"
+ "\t-F\t\tRun in foreground, do not run as daemon\n"
+ "\t-z\t\tKill other config daemon and exit\n"
+ "\t-a UNIX|IPv4|IPv6 Internal backend socket family\n"
+ "\t-u \tInternal socket domain path or IP addr (see -a)(default: %s)\n"
+ "\t-P \tPid filename (default: %s)\n"
+ "\t-1\t\tRun once and then quit (dont wait for events)\n"
+ "\t-s \tSpecify backend startup mode: none|startup|running|init)\n"
+ "\t-c \tLoad extra xml configuration, but don't commit.\n"
+ "\t-g \tClient membership required to this group (default: %s)\n"
+
+ "\t-y \tLoad yang spec file (override yang main module)\n"
+ "\t-x \tXMLDB plugin\n",
argv0,
plgdir ? plgdir : "none",
confsock ? confsock : "none",
@@ -185,7 +196,6 @@ db_merge(clicon_handle h,
return retval;
}
-
/*! Create backend server socket and register callback
*/
static int
@@ -205,45 +215,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,
@@ -293,7 +264,7 @@ nacm_load_external(clicon_handle h)
}
if ((yspec = yspec_new()) == NULL)
goto done;
- if (yang_parse(h, CLIXON_DATADIR, "ietf-netconf-acm", NULL, yspec) < 0)
+ if (yang_parse(h, NULL, "ietf-netconf-acm", CLIXON_DATADIR, NULL, yspec, NULL) < 0)
goto done;
fd = fileno(f);
/* Read configfile */
@@ -553,9 +524,13 @@ main(int argc,
int xml_pretty;
char *xml_format;
char *nacm_mode;
+ int logdst = CLICON_LOG_SYSLOG|CLICON_LOG_STDERR;
+ yang_spec *yspec = NULL;
+ yang_spec *yspecfg = NULL; /* For config XXX clixon bug */
+ char *yang_filename = NULL;
/* In the startup, logs to stderr & syslog and debug flag set later */
- clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR|CLICON_LOG_SYSLOG);
+ clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
/* Initiate CLICON handle */
if ((h = backend_handle_init()) == NULL)
return -1;
@@ -571,7 +546,6 @@ main(int argc,
optind = 1;
while ((c = getopt(argc, argv, BACKEND_OPTS)) != -1)
switch (c) {
- case '?':
case 'h':
/* Defer the call to usage() to later. Reason is that for helpful
text messages, default dirs, etc, are not set until later.
@@ -589,6 +563,14 @@ main(int argc,
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
break;
+ case 'l': /* Log destination: s|e|o */
+ if ((logdst = clicon_log_opt(optarg[0])) < 0)
+ usage(h, argv[0]);
+ if (logdst == CLICON_LOG_FILE &&
+ strlen(optarg)>1 &&
+ clicon_log_file(optarg+1) < 0)
+ goto done;
+ break;
}
/*
* Here we have the debug flag settings, use that.
@@ -597,15 +579,20 @@ main(int argc,
* XXX: if started in a start-daemon script, there will be irritating
* double syslogs until fork below.
*/
- clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_SYSLOG);
+ clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, logdst);
clicon_debug_init(debug, NULL);
+ /* Create configure yang-spec */
+ if ((yspecfg = yspec_new()) == NULL)
+ goto done;
+
/* Find and read configfile */
- if (clicon_options_main(h) < 0){
+ if (clicon_options_main(h, yspecfg) < 0){
if (help)
usage(h, argv[0]);
return -1;
}
+ clicon_config_yang_set(h, yspecfg);
/* External NACM file? */
nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE");
if (nacm_mode && strcmp(nacm_mode, "external") == 0)
@@ -617,8 +604,10 @@ main(int argc,
optind = 1;
while ((c = getopt(argc, argv, BACKEND_OPTS)) != -1)
switch (c) {
+ case 'h' : /* help */
case 'D' : /* debug */
case 'f': /* config file */
+ case 'l' :
break; /* see above */
case 'd': /* Plugin directory */
if (!strlen(optarg))
@@ -633,12 +622,12 @@ main(int argc,
case 'F' : /* foreground */
foreground = 1;
break;
- case '1' : /* Quit after reading database once - dont wait for events */
- once = 1;
- break;
case 'z': /* Zap other process */
zap++;
break;
+ case 'a': /* internal backend socket address family */
+ clicon_option_str_set(h, "CLICON_SOCK_FAMILY", optarg);
+ break;
case 'u': /* config unix domain path / ip address */
if (!strlen(optarg))
usage(h, argv[0]);
@@ -647,6 +636,9 @@ main(int argc,
case 'P': /* pidfile */
clicon_option_str_set(h, "CLICON_BACKEND_PIDFILE", optarg);
break;
+ case '1' : /* Quit after reading database once - dont wait for events */
+ once = 1;
+ break;
case 's' : /* startup mode */
clicon_option_str_set(h, "CLICON_STARTUP_MODE", optarg);
if (clicon_startup_mode(h) < 0){
@@ -660,8 +652,8 @@ main(int argc,
case 'g': /* config socket group */
clicon_option_str_set(h, "CLICON_SOCK_GROUP", optarg);
break;
- case 'y' :{ /* Override yang module or absolute filename */
- clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", optarg);
+ case 'y' :{ /* Load yang spec file (override yang main module) */
+ yang_filename = optarg;
break;
}
case 'x' :{ /* xmldb plugin */
@@ -676,6 +668,8 @@ main(int argc,
argc -= optind;
argv += optind;
+ clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, logdst);
+
/* Defer: Wait to the last minute to print help message */
if (help)
usage(h, argv[0]);
@@ -735,6 +729,13 @@ main(int argc,
return -1;
}
+ /* Publish stream on pubsub channels.
+ * CLICON_STREAM_PUB should be set to URL to where streams are published
+ * and configure should be run with --enable-publish
+ */
+ if (clicon_option_exists(h, "CLICON_STREAM_PUB") &&
+ stream_publish_init() < 0)
+ goto done;
if ((xmldb_plugin = clicon_xmldb_plugin(h)) == NULL){
clicon_log(LOG_ERR, "No xmldb plugin given (specify option CLICON_XMLDB_PLUGIN).\n");
goto done;
@@ -744,10 +745,37 @@ main(int argc,
/* Connect to plugin to get a handle */
if (xmldb_connect(h) < 0)
goto done;
- /* Parse db spec file */
- if (yang_spec_main(h) == NULL)
- goto done;
+ /* Create top-level yang spec and store as option */
+ if ((yspec = yspec_new()) == NULL)
+ goto done;
+ clicon_dbspec_yang_set(h, yspec);
+ /* Load main application yang specification either module or specific file
+ * If -y is given, it overrides main module */
+ if (yang_filename){
+ if (yang_spec_parse_file(h, yang_filename, clicon_yang_dir(h), yspec, NULL) < 0)
+ goto done;
+ }
+ else if (yang_spec_parse_module(h, clicon_yang_module_main(h),
+ clicon_yang_dir(h),
+ clicon_yang_module_revision(h),
+ yspec, NULL) < 0)
+ goto done;
+
+ /* Load yang module library, RFC7895 */
+ if (yang_modules_init(h) < 0)
+ goto done;
+ /* Add netconf yang spec, used by netconf client and as internal protocol */
+ if (netconf_module_load(h) < 0)
+ goto done;
+ /* Load yang Restconf stream discovery */
+ if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") &&
+ yang_spec_parse_module(h, "ietf-restconf-monitoring", CLIXON_DATADIR, NULL, yspec, NULL)< 0)
+ goto done;
+ /* Load yang Netconf stream discovery */
+ if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") &&
+ yang_spec_parse_module(h, "ietf-netconf-notification", CLIXON_DATADIR, NULL, yspec, NULL)< 0)
+ goto done;
/* Set options: database dir and yangspec (could be hidden in connect?)*/
if (xmldb_setopt(h, "dbdir", clicon_xmldb_dir(h)) < 0)
goto done;
@@ -815,9 +843,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");
@@ -835,6 +860,8 @@ main(int argc,
if (debug)
clicon_option_dump(h, debug);
+ if (stream_timer_setup(0, h) < 0)
+ goto done;
if (event_loop() < 0)
goto done;
retval = 0;
diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c
index 6dd23d4d..e8ce2972 100644
--- a/apps/backend/backend_plugin.c
+++ b/apps/backend/backend_plugin.c
@@ -100,7 +100,7 @@ clixon_plugin_reset(clicon_handle h,
if ((resetfn = cp->cp_api.ca_reset) == NULL)
continue;
if ((retval = resetfn(h, db)) < 0) {
- clicon_debug(1, "plugin_start() failed\n");
+ clicon_debug(1, "plugin_start() failed");
return -1;
}
break;
@@ -112,6 +112,7 @@ clixon_plugin_reset(clicon_handle h,
* This is internal system call, plugin is invoked (does not call) this function
* Backend plugins can register
* @param[in] h clicon handle
+ * @param[in] yspec Yang spec
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[in,out] xtop State XML tree is merged with existing tree.
* @retval -1 Error
@@ -120,29 +121,17 @@ clixon_plugin_reset(clicon_handle h,
* @note xtop can be replaced
*/
int
-clixon_plugin_statedata(clicon_handle h,
- char *xpath,
- cxobj **xtop)
+clixon_plugin_statedata(clicon_handle h,
+ yang_spec *yspec,
+ char *xpath,
+ cxobj **xret)
{
- int retval = -1;
- int i;
- cxobj *x = NULL;
- yang_spec *yspec;
- cxobj **xvec = NULL;
- size_t xlen;
- cxobj *xc;
+ int retval = -1;
+ int ret;
+ cxobj *x = NULL;
clixon_plugin *cp = NULL;
plgstatedata_t *fn; /* Plugin statedata fn */
- char *reason = NULL;
- if ((yspec = clicon_dbspec_yang(h)) == NULL){
- clicon_err(OE_YANG, ENOENT, "No yang spec");
- goto done;
- }
- if (*xtop==NULL){
- clicon_err(OE_CFG, ENOENT, "XML tree expected");
- goto done;
- }
while ((cp = clixon_plugin_each(h, cp)) != NULL) {
if ((fn = cp->cp_api.ca_statedata) == NULL)
continue;
@@ -152,50 +141,19 @@ clixon_plugin_statedata(clicon_handle h,
retval = 1;
goto done; /* Dont quit here on user callbacks */
}
- if (xml_merge(*xtop, x, yspec, &reason) < 0)
+ if ((ret = netconf_trymerge(x, yspec, xret)) != 0){
+ retval = ret;
goto done;
- if (reason){
- while ((xc = xml_child_i(*xtop, 0)) != NULL)
- xml_purge(xc);
- clicon_log(LOG_NOTICE, "%s: Plugin '%s' state callback failed",
- __FUNCTION__, cp->cp_name);
- if (netconf_operation_failed_xml(xtop, "rpc", reason)< 0)
- goto done;
- goto ok;
}
if (x){
xml_free(x);
x = NULL;
}
}
- /* Code complex to filter out anything that is outside of xpath */
- if (xpath_vec(*xtop, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
- goto done;
-
- /* If vectors are specified then mark the nodes found and
- * then filter out everything else,
- * otherwise return complete tree.
- */
- if (xvec != NULL){
- for (i=0; icp_name);
- break;
+ break;
}
}
return retval;
@@ -357,7 +315,7 @@ plugin_transaction_revert(clicon_handle h,
while ((cp = clixon_plugin_each_revert(h, cp, nr)) != NULL) {
if ((fn = cp->cp_api.ca_trans_commit) == NULL)
continue;
- if ((retval = fn(h, (transaction_data)td)) < 0){
+ if ((retval = fn(h, (transaction_data)&tr)) < 0){
clicon_log(LOG_NOTICE, "%s: Plugin '%s' trans_commit revert callback failed",
__FUNCTION__, cp->cp_name);
break;
diff --git a/apps/backend/backend_plugin.h b/apps/backend/backend_plugin.h
index 3dff37b2..a2736b19 100644
--- a/apps/backend/backend_plugin.h
+++ b/apps/backend/backend_plugin.h
@@ -71,7 +71,7 @@ int backend_plugin_initiate(clicon_handle h);
int clixon_plugin_reset(clicon_handle h, char *db);
-int clixon_plugin_statedata(clicon_handle h, char *xpath, cxobj **xtop);
+int clixon_plugin_statedata(clicon_handle h, yang_spec *yspec, char *xpath, cxobj **xtop);
transaction_data_t * transaction_new(void);
int transaction_free(transaction_data_t *);
diff --git a/apps/backend/backend_socket.c b/apps/backend/backend_socket.c
index a2587044..ec32fb53 100644
--- a/apps/backend/backend_socket.c
+++ b/apps/backend/backend_socket.c
@@ -74,7 +74,8 @@
#include "backend_handle.h"
static int
-config_socket_init_ipv4(clicon_handle h, char *dst)
+config_socket_init_ipv4(clicon_handle h,
+ char *dst)
{
int s;
struct sockaddr_in addr;
@@ -116,7 +117,8 @@ config_socket_init_ipv4(clicon_handle h, char *dst)
* and group according to CLICON_SOCK_GROUP option.
*/
static int
-config_socket_init_unix(clicon_handle h, char *sock)
+config_socket_init_unix(clicon_handle h,
+ char *sock)
{
int s;
struct sockaddr_un addr;
@@ -197,7 +199,7 @@ backend_socket_init(clicon_handle h)
*/
int
backend_accept_client(int fd,
- void *arg)
+ void *arg)
{
int retval = -1;
clicon_handle h = (clicon_handle)arg;
diff --git a/apps/backend/clixon_backend_handle.c b/apps/backend/clixon_backend_handle.c
index 1fc397d8..8ed13d97 100644
--- a/apps/backend/clixon_backend_handle.c
+++ b/apps/backend/clixon_backend_handle.c
@@ -86,10 +86,11 @@ struct backend_handle {
int bh_magic; /* magic (HDR)*/
clicon_hash_t *bh_copt; /* clicon option list (HDR) */
clicon_hash_t *bh_data; /* internal clicon data (HDR) */
+ event_stream_t *bh_stream; /* notification streams, see clixon_stream.[ch] */
+
/* ------ 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 */
};
@@ -103,6 +104,7 @@ backend_handle_init(void)
/*! Deallocates a backend handle, including all client structs
* @Note: handle 'h' cannot be used in calls after this
+ * @see backend_client_rm
*/
int
backend_handle_exit(clicon_handle h)
@@ -110,164 +112,20 @@ backend_handle_exit(clicon_handle h)
struct backend_handle *bh = handle(h);
struct client_entry *ce;
- /* only delete client structs, not close sockets, etc, see backend_client_rm */
- while ((ce = backend_client_list(h)) != NULL)
+ /* only delete client structs, not close sockets, etc, see backend_client_rm WHY NOT? */
+ while ((ce = backend_client_list(h)) != NULL){
+ if (ce->ce_s){
+ close(ce->ce_s);
+ ce->ce_s = 0;
+ }
backend_client_delete(h, ce);
+ }
if (bh->bh_nacm)
xml_free(bh->bh_nacm);
- clicon_handle_exit(h); /* frees h and options */
+ clicon_handle_exit(h); /* frees h and options (and streams) */
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
@@ -330,111 +188,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 3855a14b..bdfbb425 100644
--- a/apps/backend/clixon_backend_handle.h
+++ b/apps/backend/clixon_backend_handle.h
@@ -43,37 +43,9 @@
/*
* Types
*/
-/* subscription callback */
-typedef int (*subscription_fn_t)(clicon_handle, void *filter, void *arg);
-
-/* 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/cli/cli_common.c b/apps/cli/cli_common.c
index 03ba734c..7e6f1307 100644
--- a/apps/cli/cli_common.c
+++ b/apps/cli/cli_common.c
@@ -1038,79 +1038,6 @@ cli_notify(clicon_handle h,
return retval;
}
-/* Backward compatible Set if you want to enable "v" cli callback functions,
- * such as cli_setv()
- * This was obsoleted in 3.7.
- * @see include/clixon_custom.h
- */
-#ifdef COMPAT_CLIV
-int cli_setv(clicon_handle h, cvec *vars, cvec *argv)
-{
- return cli_set(h, vars, argv);
-}
-int cli_mergev(clicon_handle h, cvec *vars, cvec *argv)
-{
- return cli_merge(h, vars, argv);
-}
-int cli_delv(clicon_handle h, cvec *vars, cvec *argv)
-{
- return cli_del(h, vars, argv);
-}
-int cli_debug_cliv(clicon_handle h, cvec *vars, cvec *argv)
-{
- return cli_debug_cli(h, vars, argv);
-}
-int cli_debug_backendv(clicon_handle h, cvec *vars, cvec *argv)
-{
- return cli_debug_backend(h, vars, argv);
-}
-int cli_set_modev(clicon_handle h, cvec *vars, cvec *argv)
-{
- return cli_set_mode(h, vars, argv);
-}
-int cli_start_shellv(clicon_handle h, cvec *vars, cvec *argv)
-{
- return cli_start_shell(h, vars, argv);
-}
-int cli_quitv(clicon_handle h, cvec *vars, cvec *argv)
-{
- return cli_quit(h, vars, argv);
-}
-int cli_commitv(clicon_handle h, cvec *vars, cvec *argv)
-{
- return cli_commit(h, vars, argv);
-}
-int cli_validatev(clicon_handle h, cvec *vars, cvec *argv)
-{
- return cli_validate(h, vars, argv);
-
-}
-int compare_dbsv(clicon_handle h, cvec *vars, cvec *argv)
-{
- return compare_dbs(h, vars, argv);
-}
-int load_config_filev(clicon_handle h, cvec *vars, cvec *argv)
-{
- return load_config_file(h, vars, argv);
-}
-int save_config_filev(clicon_handle h, cvec *vars, cvec *argv)
-{
- return save_config_file(h, vars, argv);
-}
-int delete_allv(clicon_handle h, cvec *vars, cvec *argv)
-{
- return delete_all(h, vars, argv);
-}
-int discard_changesv(clicon_handle h, cvec *vars, cvec *argv)
-{
- return discard_changes(h, vars, argv);
-}
-int cli_notifyv(clicon_handle h, cvec *vars, cvec *argv)
-{
- return cli_notify(h, vars, argv);
-}
-#endif /* COMPAT_CLIV */
-
/*! Lock database
*
* @param[in] h Clicon handle
@@ -1249,23 +1176,6 @@ cli_copy_config(clicon_handle h,
for (i=0; i");
if (helptext)
cprintf(cb, "(\"%s\")", helptext);
- cprintf(cb, "|<%s:%s choice:", ys->ys_argument, cvtypestr);
if ((ybaseref = yang_find((yang_node*)ytype, Y_BASE, NULL)) != NULL &&
(ybaseid = yang_find_identity(ys, ybaseref->ys_argument)) != NULL){
- i = 0;
- while ((cv = cvec_each(ybaseid->ys_cvec, cv)) != NULL){
- if (i++)
- cprintf(cb, "|");
- name = strdup(cv_name_get(cv));
- if ((id=strchr(name, ':')) != NULL)
- *id = '\0';
- cprintf(cb, "%s:%s", name, id+1);
- if (name)
- free(name);
+ if (cvec_len(ybaseid->ys_cvec) > 0){
+ cprintf(cb, "|<%s:%s choice:", ys->ys_argument, cvtypestr);
+ i = 0;
+ while ((cv = cvec_each(ybaseid->ys_cvec, cv)) != NULL){
+ if (i++)
+ cprintf(cb, "|");
+ name = strdup(cv_name_get(cv));
+ if ((id=strchr(name, ':')) != NULL)
+ *id = '\0';
+ cprintf(cb, "%s:%s", name, id+1);
+ if (name)
+ free(name);
+ }
}
}
}
@@ -792,7 +794,7 @@ yang2cli(clicon_handle h,
if (yang2cli_stmt(h, ymod, cbuf, gt, 0) < 0)
goto done;
}
- clicon_debug(1, "%s: buf\n%s\n", __FUNCTION__, cbuf_get(cbuf));
+ clicon_debug(0, "%s: buf\n%s\n", __FUNCTION__, cbuf_get(cbuf));
/* Parse the buffer using cligen parser. XXX why this?*/
if ((globals = cvec_new(0)) == NULL)
goto done;
diff --git a/apps/cli/cli_handle.c b/apps/cli/cli_handle.c
index 1690a143..58e03948 100644
--- a/apps/cli/cli_handle.c
+++ b/apps/cli/cli_handle.c
@@ -76,14 +76,14 @@
* @see struct clicon_handle, struct backend_handle
*/
struct cli_handle {
- int cl_magic; /* magic (HDR)*/
- clicon_hash_t *cl_copt; /* clicon option list (HDR) */
- clicon_hash_t *cl_data; /* internal clicon data (HDR) */
+ int cl_magic; /* magic (HDR)*/
+ clicon_hash_t *cl_copt; /* clicon option list (HDR) */
+ clicon_hash_t *cl_data; /* internal clicon data (HDR) */
+ event_stream_t *cl_stream; /* notification streams, see clixon_stream.[ch] */
/* ------ end of common handle ------ */
- cligen_handle cl_cligen; /* cligen handle */
-
- cli_syntax_t *cl_stx; /* syntax structure */
+ cligen_handle cl_cligen; /* cligen handle */
+ cli_syntax_t *cl_stx; /* syntax structure */
};
/*! Return a clicon handle for other CLICON API calls
diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c
index bd10b8f8..c651127e 100644
--- a/apps/cli/cli_main.c
+++ b/apps/cli/cli_main.c
@@ -71,19 +71,27 @@
#include "cli_handle.h"
/* Command line options to be passed to getopt(3) */
-#define CLI_OPTS "hD:f:xl:F:1u:d:m:qpGLy:c:U:"
+#define CLI_OPTS "hD:f:xl:F:1a:u:d:m:qpGLy:c:U:"
+
+#define CLI_LOGFILE "/tmp/clixon_cli.log"
/*! terminate cli application */
static int
cli_terminate(clicon_handle h)
{
- yang_spec *yspec;
+ yang_spec *yspec;
+ cxobj *x;
clicon_rpc_close_session(h);
if ((yspec = clicon_dbspec_yang(h)) != NULL)
yspec_free(yspec);
+ if ((yspec = clicon_config_yang(h)) != NULL)
+ yspec_free(yspec);
+ if ((x = clicon_conf_xml(h)) != NULL)
+ xml_free(x);
cli_plugin_finish(h);
cli_handle_exit(h);
+ clicon_log_exit();
return 0;
}
@@ -197,31 +205,30 @@ static void
usage(clicon_handle h,
char *argv0)
{
- char *confsock = clicon_sock(h);
char *plgdir = clicon_cli_dir(h);
fprintf(stderr, "usage:%s [options] [commands]\n"
"where commands is a CLI command or options passed to the main plugin\n"
"where options are\n"
"\t-h \t\tHelp\n"
- "\t-D \tDebug\n"
+ "\t-D \tDebug level\n"
"\t-f \tConfig-file (mandatory)\n"
"\t-x\t\tDump configuration file as XML on stdout (migration utility)\n"
"\t-F \tRead commands from file (default stdin)\n"
"\t-1\t\tDo not enter interactive mode\n"
- "\t-u \tconfig UNIX domain path (default: %s)\n"
+ "\t-a UNIX|IPv4|IPv6\tInternal backend socket family\n"
+ "\t-u \tInternal socket domain path or IP addr (see -a)\n"
"\t-d \tSpecify plugin directory (default: %s)\n"
"\t-m \tSpecify plugin syntax mode\n"
"\t-q \t\tQuiet mode, dont print greetings or prompt, terminate on ctrl-C\n"
"\t-p \t\tPrint database yang specification\n"
"\t-G \t\tPrint CLI syntax generated from dbspec (if CLICON_CLI_GENMODEL enabled)\n"
"\t-L \t\tDebug print dynamic CLI syntax including completions and expansions\n"
- "\t-l \tLog on (s)yslog, std(e)rr or std(o)ut (stderr is default)\n"
+ "\t-l > \tLog on (s)yslog, std(e)rr, std(o)ut or (f)ile (stderr is default)\n"
"\t-y \tOverride yang spec file (dont include .yang suffix)\n"
"\t-c \tSpecify cli spec file.\n"
"\t-U \tOver-ride unix user with a pseudo user for NACM.\n",
argv0,
- confsock ? confsock : "none",
plgdir ? plgdir : "none"
);
exit(1);
@@ -248,7 +255,10 @@ main(int argc, char **argv)
char *restarg = NULL; /* what remains after options */
int dump_configfile_xml = 0;
yang_spec *yspec;
+ yang_spec *yspecfg = NULL; /* For config XXX clixon bug */
struct passwd *pw;
+ char *yang_filename = NULL;
+ yang_stmt *ymod = NULL; /* Main module */
/* Defaults */
once = 0;
@@ -297,21 +307,14 @@ main(int argc, char **argv)
case 'x': /* dump config file as xml (migration from .conf file)*/
dump_configfile_xml++;
break;
- case 'l': /* Log destination: s|e|o */
- switch (optarg[0]){
- case 's':
- logdst = CLICON_LOG_SYSLOG;
- break;
- case 'e':
- logdst = CLICON_LOG_STDERR;
- break;
- case 'o':
- logdst = CLICON_LOG_STDOUT;
- break;
- default:
- usage(h, argv[0]);
- }
- break;
+ case 'l': /* Log destination: s|e|o|f */
+ if ((logdst = clicon_log_opt(optarg[0])) < 0)
+ usage(h, argv[0]);
+ if (logdst == CLICON_LOG_FILE &&
+ strlen(optarg)>1 &&
+ clicon_log_file(optarg+1) < 0)
+ goto done;
+ break;
}
/*
* Logs, error and debug to stderr or syslog, set debug level
@@ -328,13 +331,16 @@ main(int argc, char **argv)
goto done;
}
+ /* Create top-level yang spec and store as option */
+ if ((yspecfg = yspec_new()) == NULL)
+ goto done;
/* Find and read configfile */
- if (clicon_options_main(h) < 0){
+ if (clicon_options_main(h, yspecfg) < 0){
if (help)
usage(h, argv[0]);
return -1;
}
-
+ clicon_config_yang_set(h, yspecfg);
/* Now rest of options */
opterr = 0;
optind = 1;
@@ -354,7 +360,10 @@ main(int argc, char **argv)
case '1' : /* Quit after reading database once - dont wait for events */
once = 1;
break;
- case 'u': /* config unix domain path/ ip host */
+ case 'a': /* internal backend socket address family */
+ clicon_option_str_set(h, "CLICON_SOCK_FAMILY", optarg);
+ break;
+ case 'u': /* internal backend socket unix domain path or ip host */
if (!strlen(optarg))
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_SOCK", optarg);
@@ -381,8 +390,8 @@ main(int argc, char **argv)
case 'L' : /* Debug print dynamic CLI syntax */
logclisyntax++;
break;
- case 'y' :{ /* Overwrite yang module or absolute filename */
- clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", optarg);
+ case 'y' :{ /* Load yang spec file (override yang main module) */
+ yang_filename = optarg;
break;
}
case 'c' :{ /* Overwrite clispec with absolute filename */
@@ -416,8 +425,24 @@ main(int argc, char **argv)
*/
cv_exclude_keys(clicon_cli_varonly(h));
- /* Parse db specification as cli*/
- if ((yspec = yang_spec_main(h)) == NULL)
+ /* Create top-level and store as option */
+ if ((yspec = yspec_new()) == NULL)
+ goto done;
+ clicon_dbspec_yang_set(h, yspec);
+
+ /* Load main application yang specification either module or specific file
+ * If -y is given, it overrides main module */
+ if (yang_filename){
+ if (yang_spec_parse_file(h, yang_filename, clicon_yang_dir(h), yspec, &ymod) < 0)
+ goto done;
+ }
+ else if (yang_spec_parse_module(h, clicon_yang_module_main(h),
+ clicon_yang_dir(h),
+ clicon_yang_module_revision(h),
+ yspec, &ymod) < 0)
+ goto done;
+ /* Load yang module library, RFC7895 */
+ if (yang_modules_init(h) < 0)
goto done;
if (printspec)
yang_print(stdout, (yang_node*)yspec);
@@ -426,18 +451,22 @@ main(int argc, char **argv)
* the only one.
*/
if (clicon_cli_genmodel(h)){
- parse_tree pt = {0,}; /* cli parse tree */
+ parse_tree pt = {0,}; /* cli parse tree */
+ char *name; /* main module name */
/* Create cli command tree from dbspec */
if (yang2cli(h, yspec, &pt, clicon_cli_genmodel_type(h)) < 0)
goto done;
- len = strlen("datamodel:") + strlen(clicon_dbspec_name(h)) + 1;
+ /* name of main module */
+ name = ymod->ys_argument;
+
+ len = strlen("datamodel:") + strlen(name) + 1;
if ((treename = malloc(len)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
- snprintf(treename, len, "datamodel:%s", clicon_dbspec_name(h));
+ snprintf(treename, len, "datamodel:%s", name);
cligen_tree_add(cli_cligen(h), treename, pt);
if (printgen)
diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c
index f5781f87..0fb58736 100644
--- a/apps/cli/cli_plugin.c
+++ b/apps/cli/cli_plugin.c
@@ -715,7 +715,17 @@ prompt_fmt(char *prompt,
cprintf(cb, "%%");
cprintf(cb, "%c", *s);
}
- }
+ }
+ else if (*s == '\\' && *++s) {
+ switch(*s) {
+ case 'n':
+ cprintf(cb, "\n");
+ break;
+ default:
+ cprintf(cb, "\\");
+ cprintf(cb, "%c", *s);
+ }
+ }
else
cprintf(cb, "%c", *s);
s++;
diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c
index 12d8c800..2e56f329 100644
--- a/apps/cli/cli_show.c
+++ b/apps/cli/cli_show.c
@@ -686,13 +686,4 @@ cli_show_auto(clicon_handle h,
return retval;
}
-#ifdef COMPAT_CLIV
-int show_yangv(clicon_handle h, cvec *vars, cvec *argv)
-{
- return show_yang(h, vars, argv);
-}
-int show_confv_xpath(clicon_handle h, cvec *vars, cvec *argv)
-{
- return show_conf_xpath(h, vars, argv);
-}
-#endif /* COMPAT_CLIV */
+
diff --git a/apps/cli/clixon_cli_api.h b/apps/cli/clixon_cli_api.h
index c31645ba..3507e31f 100644
--- a/apps/cli/clixon_cli_api.h
+++ b/apps/cli/clixon_cli_api.h
@@ -144,25 +144,4 @@ int cli_show_config(clicon_handle h, cvec *cvv, cvec *argv);
int cli_show_auto(clicon_handle h, cvec *cvv, cvec *argv);
-#ifdef COMPAT_CLIV
-int cli_setv(clicon_handle h, cvec *vars, cvec *argv);
-int cli_mergev(clicon_handle h, cvec *vars, cvec *argv);
-int cli_delv(clicon_handle h, cvec *vars, cvec *argv);
-int cli_debug_cliv(clicon_handle h, cvec *vars, cvec *argv);
-int cli_debug_backendv(clicon_handle h, cvec *vars, cvec *argv);
-int cli_set_modev(clicon_handle h, cvec *vars, cvec *argv);
-int cli_start_shellv(clicon_handle h, cvec *vars, cvec *argv);
-int cli_quitv(clicon_handle h, cvec *vars, cvec *argv);
-int cli_commitv(clicon_handle h, cvec *vars, cvec *argv);
-int cli_validatev(clicon_handle h, cvec *vars, cvec *argv);
-int compare_dbsv(clicon_handle h, cvec *vars, cvec *argv);
-int load_config_filev(clicon_handle h, cvec *vars, cvec *argv);
-int save_config_filev(clicon_handle h, cvec *vars, cvec *argv);
-int delete_allv(clicon_handle h, cvec *vars, cvec *argv);
-int discard_changesv(clicon_handle h, cvec *vars, cvec *argv);
-int cli_notifyv(clicon_handle h, cvec *cvv, cvec *argv);
-int show_yangv(clicon_handle h, cvec *vars, cvec *argv);
-int show_confv_xpath(clicon_handle h, cvec *cvv, cvec *argv);
-#endif /* COMPAT_CLIV */
-
#endif /* _CLIXON_CLI_API_H_ */
diff --git a/apps/netconf/netconf_hello.c b/apps/netconf/netconf_hello.c
index 9fc0c2c2..1be1211c 100644
--- a/apps/netconf/netconf_hello.c
+++ b/apps/netconf/netconf_hello.c
@@ -105,28 +105,82 @@ netconf_hello_dispatch(cxobj *xn)
return retval;
}
-/*
- * netconf_create_hello
- * create capability string (once)
+/*! Create Netconf hello. Single cap and defer individual to querying modules
+
+ * @param[in] h Clicon handle
+ * @param[in] cb Msg buffer
+ * @param[in] session_id Id of client session
+ * Lots of dependencies here. regarding the hello protocol.
+ * RFC6241 NETCONF Protocol says: (8.1)
+ * MUST send a element containing a list of that peer's capabilities
+ * MUST send at least the base NETCONF capability, urn:ietf:params:netconf:base:1.1
+ * MAY include capabilities for previous NETCONF versions
+ * MUST include a
+ * the example shows urn:ietf:params:netconf:capability:startup:1.0
+
+ * RFC5277 NETCONF Event Notifications
+ * urn:ietf:params:netconf:capability:notification:1.0 is advertised during the capability exchange
+ *
+ * RFC6022 YANG Module for NETCONF Monitoring
+ * MUST advertise the capability URI "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring"
+ * RFC7895 Yang module library defines how to announce module features (not hell capabilities)
+ * RFC7950 YANG 1.1 says (5.6.4);
+ * MUST announce the modules it implements by implementing the YANG module
+ * "ietf-yang-library" (RFC7895) and listing all implemented modules in the
+ * "/modules-state/module" list.
+ * MUST advertise urn:ietf:params:netconf:capability:yang-library:1.0?
+ * revision=&module-set-id= in the message.
+ *
+ * Question: should the NETCONF in RFC6241 sections 8.2-8.9 be announced both
+ * as features and as capabilities in the message according to RFC6241?
+ * urn:ietf:params:netconf:capability:candidate:1:0 (8.3)
+ * urn:ietf:params:netconf:capability:validate:1.1 (8.6)
+ * urn:ietf:params:netconf:capability:startup:1.0 (8.7)
+ * urn:ietf:params:netconf:capability:xpath:1.0 (8.9)
+ * urn:ietf:params:netconf:capability:notification:1.0 (RFC5277)
+ *
+ * @note the hello message is created bythe netconf application, not the
+ * backend, and backend may implement more modules - please consider if using
+ * library routines for detecting capabilities here. In contrast, yang module
+ * list (RFC7895) is processed by the backend.
+ * @note encode bodies, see xml_chardata_encode()
+ * @see yang_modules_state_get
+ * @see netconf_module_load
*/
int
-netconf_create_hello(cbuf *xf, /* msg buffer */
- int session_id)
+netconf_create_hello(clicon_handle h,
+ cbuf *cb,
+ int session_id)
{
- int retval = 0;
+ int retval = -1;
+ char *module_set_id;
+ char *ietf_yang_library_revision;
+ char *encstr = NULL;
- add_preamble(xf);
- cprintf(xf, "");
- cprintf(xf, "");
- cprintf(xf, "urn:ietf:params:xml:ns:netconf:base:1.0\n");
- cprintf(xf, "urn:ietf:params:xml:ns:netconf:capability:candidate:1:0\n");
- cprintf(xf, "urn:ietf:params:xml:ns:netconf:capability:validate:1.0\n");
- cprintf(xf, "urn:ietf:params:netconf:capability:xpath:1.0\n");
- cprintf(xf, "urn:ietf:params:netconf:capability:notification:1.0\n");
- cprintf(xf, "urn:ietf:params:netconf:capability:startup:1.0\n");
- cprintf(xf, "");
- cprintf(xf, "%lu", (long unsigned int)42+session_id);
- cprintf(xf, "");
- add_postamble(xf);
+ module_set_id = clicon_option_str(h, "CLICON_MODULE_SET_ID");
+ if ((ietf_yang_library_revision = yang_modules_revision(h)) == NULL)
+ goto done;
+ add_preamble(cb);
+ cprintf(cb, "");
+ cprintf(cb, "");
+ cprintf(cb, "urn:ietf:params:netconf:base:1.0");
+ if (xml_chardata_encode(&encstr, "urn:ietf:params:netconf:capability:yang-library:1.0?revision=%s&module-set-id=%s",
+ ietf_yang_library_revision,
+ module_set_id) < 0)
+ goto done;
+ cprintf(cb, "%s", encstr);
+ cprintf(cb, "urn:ietf:params:netconf:capability:candidate:1:0");
+ cprintf(cb, "urn:ietf:params:netconf:capability:validate:1.1");
+ cprintf(cb, "urn:ietf:params:netconf:capability:startup:1.0");
+ cprintf(cb, "urn:ietf:params:netconf:capability:xpath:1.0");
+ cprintf(cb, "urn:ietf:params:netconf:capability:notification:1.0");
+ cprintf(cb, "");
+ cprintf(cb, "%lu", (long unsigned int)session_id);
+ cprintf(cb, "");
+ add_postamble(cb);
+ retval = 0;
+ done:
+ if (encstr)
+ free(encstr);
return retval;
}
diff --git a/apps/netconf/netconf_hello.h b/apps/netconf/netconf_hello.h
index 82e235f7..4f877b32 100644
--- a/apps/netconf/netconf_hello.h
+++ b/apps/netconf/netconf_hello.h
@@ -40,7 +40,7 @@
/*
* Prototypes
*/
-int netconf_create_hello(cbuf *xf, int session_id);
+int netconf_create_hello(clicon_handle h, cbuf *cb, int session_id);
int netconf_hello_dispatch(cxobj *xn);
diff --git a/apps/netconf/netconf_lib.c b/apps/netconf/netconf_lib.c
index 998b4ef0..fc24810b 100644
--- a/apps/netconf/netconf_lib.c
+++ b/apps/netconf/netconf_lib.c
@@ -182,6 +182,9 @@ netconf_get_target(cxobj *xn,
* @param[in] s
* @param[in] cb Cligen buffer that contains the XML message
* @param[in] msg Only for debug
+ * @note Assumes "cb" contains valid XML, ie encoding is correct. This is done
+ * if it is output by a xml render routine (xml_print et al), but NOT
+ * otherwise.
*/
int
netconf_output(int s,
diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c
index 9811d959..6f3f30bc 100644
--- a/apps/netconf/netconf_main.c
+++ b/apps/netconf/netconf_main.c
@@ -71,7 +71,9 @@
#include "netconf_rpc.h"
/* Command line options to be passed to getopt(3) */
-#define NETCONF_OPTS "hDqf:d:Sy:U:"
+#define NETCONF_OPTS "hD:f:l:qa:u:d:y:U:t:"
+
+#define NETCONF_LOGFILE "/tmp/clixon_netconf.log"
/*! Process incoming packet
* @param[in] h Clicon handle
@@ -89,16 +91,18 @@ process_incoming_packet(clicon_handle h,
cxobj *xret = NULL; /* Return (out) */
cxobj *xrpc;
cxobj *xc;
+ yang_spec *yspec;
clicon_debug(1, "RECV");
clicon_debug(2, "%s: RCV: \"%s\"", __FUNCTION__, cbuf_get(cb));
+ yspec = clicon_dbspec_yang(h);
if ((str0 = strdup(cbuf_get(cb))) == NULL){
clicon_log(LOG_ERR, "%s: strdup: %s", __FUNCTION__, strerror(errno));
return -1;
}
str = str0;
/* Parse incoming XML message */
- if (xml_parse_string(str, NULL, &xreq) < 0){
+ if (xml_parse_string(str, yspec, &xreq) < 0){
if ((cbret = cbuf_new()) == NULL){
if (netconf_operation_failed(cbret, "rpc", "internal error")< 0)
goto done;
@@ -239,48 +243,60 @@ netconf_input_cb(int s,
return retval;
}
-/*
- * send_hello
- * args: s file descriptor to write on (eg 1 - stdout)
+/*! Send netconf hello message
+ * @param[in] h Clicon handle
+ * @param[in] s File descriptor to write on (eg 1 - stdout)
*/
static int
-send_hello(int s)
+send_hello(clicon_handle h,
+ int s)
{
- cbuf *xf;
- int retval = -1;
+ int retval = -1;
+ cbuf *cb;
- if ((xf = cbuf_new()) == NULL){
+ if ((cb = cbuf_new()) == NULL){
clicon_log(LOG_ERR, "%s: cbuf_new", __FUNCTION__);
goto done;
}
- if (netconf_create_hello(xf, getpid()) < 0)
+ if (netconf_create_hello(h, cb, getpid()) < 0)
goto done;
- if (netconf_output(s, xf, "hello") < 0)
+ if (netconf_output(s, cb, "hello") < 0)
goto done;
retval = 0;
done:
- if (xf)
- cbuf_free(xf);
+ if (cb)
+ cbuf_free(cb);
return retval;
}
static int
netconf_terminate(clicon_handle h)
{
- yang_spec *yspec;
-
+ yang_spec *yspec;
+ cxobj *x;
+
clixon_plugin_exit(h);
rpc_callback_delete_all();
clicon_rpc_close_session(h);
if ((yspec = clicon_dbspec_yang(h)) != NULL)
yspec_free(yspec);
- if ((yspec = clicon_netconf_yang(h)) != NULL)
+ if ((yspec = clicon_config_yang(h)) != NULL)
yspec_free(yspec);
+ if ((x = clicon_conf_xml(h)) != NULL)
+ xml_free(x);
event_exit();
clicon_handle_exit(h);
+ clicon_log_exit();
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
@@ -293,13 +309,17 @@ usage(clicon_handle h,
fprintf(stderr, "usage:%s\n"
"where options are\n"
"\t-h\t\tHelp\n"
- "\t-D\t\tDebug\n"
+ "\t-D \tDebug level\n"
"\t-q\t\tQuiet: dont send hello prompt\n"
"\t-f \tConfiguration file (mandatory)\n"
+ "\t-l (e|o|s|f) \tLog on std(e)rr, std(o)ut, (s)yslog, (f)ile (syslog is default)\n"
+ "\t-a UNIX|IPv4|IPv6\tInternal backend socket family\n"
+ "\t-u \tInternal socket domain path or IP addr (see -a)\n"
"\t-d \tSpecify netconf plugin directory dir (default: %s)\n"
- "\t-S\t\tLog on syslog\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-y \tLoad yang spec file (override yang main module)\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)
);
@@ -315,18 +335,19 @@ main(int argc,
char *argv0 = argv[0];
int quiet = 0;
clicon_handle h;
- int use_syslog;
char *dir;
+ int logdst = CLICON_LOG_STDERR;
struct passwd *pw;
+ struct timeval tv = {0,}; /* timeout */
+ yang_spec *yspec = NULL;
+ yang_spec *yspecfg = NULL; /* For config XXX clixon bug */
+ char *yang_filename = NULL;
- /* Defaults */
- use_syslog = 0;
-
- /* In the startup, logs to stderr & debug flag set later */
- clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR);
/* 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){
@@ -335,35 +356,43 @@ 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 */
usage(h, argv[0]);
break;
case 'D' : /* debug */
- debug = 1;
+ if (sscanf(optarg, "%d", &debug) != 1)
+ usage(h, argv[0]);
break;
case 'f': /* override config file */
if (!strlen(optarg))
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
break;
- case 'S': /* Log on syslog */
- use_syslog = 1;
+ case 'l': /* Log destination: s|e|o */
+ if ((logdst = clicon_log_opt(optarg[0])) < 0)
+ usage(h, argv[0]);
+ if (logdst == CLICON_LOG_FILE &&
+ strlen(optarg)>1 &&
+ clicon_log_file(optarg+1) < 0)
+ goto done;
break;
}
+
/*
* Logs, error and debug to stderr or syslog, set debug level
*/
- clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO,
- use_syslog?CLICON_LOG_SYSLOG:CLICON_LOG_STDERR);
+ clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, logdst);
clicon_debug_init(debug, NULL);
+ /* Create configure yang-spec */
+ if ((yspecfg = yspec_new()) == NULL)
+ goto done;
/* Find and read configfile */
- if (clicon_options_main(h) < 0)
+ if (clicon_options_main(h, yspecfg) < 0)
return -1;
-
+ clicon_config_yang_set(h, yspecfg);
/* Now rest of options */
optind = 1;
opterr = 0;
@@ -371,9 +400,17 @@ main(int argc,
switch (c) {
case 'h' : /* help */
case 'D' : /* debug */
- case 'f': /* config file */
- case 'S': /* Log on syslog */
+ case 'f': /* config file */
+ case 'l': /* log */
break; /* see above */
+ case 'a': /* internal backend socket address family */
+ clicon_option_str_set(h, "CLICON_SOCK_FAMILY", optarg);
+ break;
+ case 'u': /* internal backend socket unix domain path or ip host */
+ if (!strlen(optarg))
+ usage(h, argv[0]);
+ clicon_option_str_set(h, "CLICON_SOCK", optarg);
+ break;
case 'q': /* quiet: dont write hello */
quiet++;
break;
@@ -382,8 +419,8 @@ main(int argc,
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_NETCONF_DIR", optarg);
break;
- case 'y' :{ /* Overwrite yang module or absolute filename */
- clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", optarg);
+ case 'y' :{ /* Load yang spec file (override yang main module) */
+ yang_filename = optarg;
break;
}
case 'U': /* Clixon 'pseudo' user */
@@ -392,6 +429,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;
@@ -399,16 +440,28 @@ main(int argc,
argc -= optind;
argv += optind;
-
-
- /* Parse yang database spec file */
- if (yang_spec_main(h) == NULL)
+ /* Create top-level yang spec and store as option */
+ if ((yspec = yspec_new()) == NULL)
goto done;
-
- /* Parse netconf yang spec file */
- if (yang_spec_netconf(h) == NULL)
+ clicon_dbspec_yang_set(h, yspec);
+ /* Load main application yang specification either module or specific file
+ * If -y is given, it overrides main module */
+ if (yang_filename){
+ if (yang_spec_parse_file(h, yang_filename, clicon_yang_dir(h), yspec, NULL) < 0)
+ goto done;
+ }
+ else if (yang_spec_parse_module(h, clicon_yang_module_main(h),
+ clicon_yang_dir(h),
+ clicon_yang_module_revision(h),
+ yspec, NULL) < 0)
+ goto done;
+
+ /* Load yang module library, RFC7895 */
+ if (yang_modules_init(h) < 0)
+ goto done;
+ /* Add netconf yang spec, used by netconf client and as internal protocol */
+ if (netconf_module_load(h) < 0)
goto done;
-
/* Initialize plugins group */
if ((dir = clicon_netconf_dir(h)) != NULL)
if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
@@ -421,11 +474,18 @@ main(int argc,
*(argv-1) = tmp;
if (!quiet)
- send_hello(1);
+ send_hello(h, 1);
if (event_reg_fd(0, netconf_input_cb, h, "netconf socket") < 0)
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..838f0c73 100644
--- a/apps/netconf/netconf_rpc.c
+++ b/apps/netconf/netconf_rpc.c
@@ -728,25 +728,27 @@ netconf_discard_changes(clicon_handle h,
major
+ * @see rfc5277:
+ * An event notification is sent to the client who initiated a
+ * command asynchronously when an event of
+ * interest...
+ * Parameters: eventTime type dateTime and compliant to [RFC3339]
+ * Also contains notification-specific tagged content, if any. With
+ * the exception of , the content of the notification is
+ * beyond the scope of this document.
*/
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 +758,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 +780,7 @@ netconf_notification_cb(int s,
}
fflush(stdout);
cbuf_free(cb);
+ok:
retval = 0;
done:
if (xt != NULL)
@@ -802,7 +794,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 +802,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,
@@ -838,9 +830,11 @@ netconf_create_subscription(clicon_handle h,
}
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, &s) < 0)
goto done;
+ if (xpath_first(*xret, "rpc-reply/rpc-error") != NULL)
+ goto ok;
if (event_reg_fd(s,
netconf_notification_cb,
- xfilter?xml_dup(xfilter):NULL,
+ NULL,
"notification socket") < 0)
goto done;
ok:
@@ -971,15 +965,9 @@ netconf_rpc_dispatch(clicon_handle h,
{
int retval = -1;
cxobj *xe;
- yang_spec *yspec = NULL;
char *username;
cxobj *xa;
- /* Check incoming RPC against system / netconf RPC:s */
- if ((yspec = clicon_netconf_yang(h)) == NULL){
- clicon_err(OE_YANG, ENOENT, "No netconf yang spec");
- goto done;
- }
/* Tag username on all incoming requests in case they are forwarded as internal messages
* This may be unecesary since not all are forwarded.
* It may even be wrong if something else is done with the incoming message?
diff --git a/apps/restconf/Makefile.in b/apps/restconf/Makefile.in
index d54f1255..32366f89 100644
--- a/apps/restconf/Makefile.in
+++ b/apps/restconf/Makefile.in
@@ -45,12 +45,14 @@ bindir = @bindir@
libdir = @libdir@
mandir = @mandir@
libexecdir = @libexecdir@
-wwwdir = /www-data
localstatedir = @localstatedir@
sysconfdir = @sysconfdir@
includedir = @includedir@
HOST_VENDOR = @host_vendor@
+wwwdir = @wwwdir@
+wwwuser = @wwwuser@
+
SH_SUFFIX = @SH_SUFFIX@
CLIXON_MAJOR = @CLIXON_VERSION_MAJOR@
CLIXON_MINOR = @CLIXON_VERSION_MINOR@
@@ -73,6 +75,7 @@ APPL = clixon_restconf
# Not accessible from plugin
APPSRC = restconf_main.c
APPSRC += restconf_methods.c
+APPSRC += restconf_stream.c
APPOBJ = $(APPSRC:.c=.o)
# Accessible from plugin
@@ -97,8 +100,13 @@ distclean: clean
# Put other executables in libexec/
# Also create a libexec/ directory for writeable/temporary files.
# Put config file in etc/
+# Also a rule for letting www-dir be owned by www-data, which only orks for sudo
install: install-lib $(APPL)
+ifeq ($(shell whoami),root)
+ install -d -m 0755 -o $(wwwuser) -g $(wwwuser) $(DESTDIR)$(wwwdir)
+else
install -d -m 0755 $(DESTDIR)$(wwwdir)
+endif
install -m 0755 $(INSTALLFLAGS) $(APPL) $(DESTDIR)$(wwwdir)
install-lib: $(MYLIB)
diff --git a/apps/restconf/README.md b/apps/restconf/README.md
index ce077300..972ab99f 100644
--- a/apps/restconf/README.md
+++ b/apps/restconf/README.md
@@ -1,23 +1,37 @@
# Clixon Restconf
-### Installation using Nginx
+ * [Installation](#Installation)
+ * [Streams](Streams)
+ * [Nchan Streams](Nchan)
+ * [Debugging](Debugging)
+
+### 1. Installation
+
+The examples are based on Nginx. Other reverse proxies should work but are not verified.
Ensure www-data is member of the CLICON_SOCK_GROUP (default clicon). If not, add it:
```
sudo usermod -a -G clicon www-data
```
+This implementation uses FastCGI, see http://www.mit.edu/~yandros/doc/specs/fcgi-spec.html.
+
+Download and start nginx. For example on ubuntu:
+```
+ sudo apt install ngnix
+```
+
Define nginx config file: /etc/nginx/sites-available/default
```
server {
...
location /restconf {
- root /usr/share/nginx/html/restconf;
- fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
- include fastcgi_params;
+ fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
+ include fastcgi_params;
}
}
```
+
Start nginx daemon
```
sudo /etc/init.d nginx start
@@ -62,11 +76,115 @@ olof@vandal> curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=et
curl -sX POST -d '{"interfaces":{"interface":{"name":"eth1","type":"eth","enabled":"true"}}}' http://localhost/restconf/data
```
-### Debugging
+### 2. Streams
+
+Clixon have two experimental restconf event stream implementations following
+RFC8040 Section 6 using SSE. One native and one using Nginx
+nchan. The Nchan alternaitve is described in the
+next section.
+
+The (example)[../../example/README.md] creates an EXAMPLE stream.
+
+Set the Clixon configuration options:
+```
+streams
+https://example.com
+3600
+```
+In this example, the stream EXAMPLE would be accessed with `https://example.com/streams/EXAMPLE`.
+
+The retention is configured as 1 hour, i.e., the stream replay function will only save timeseries one other.
+
+Clixon defines an internal in-memory (not persistent) replay function
+controlled by the configure option above.
+
+You may access a restconf streams using curl.
+
+Add the following to extend the nginx configuration file with the following statements (for example):
+```
+ location /streams {
+ fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
+ include fastcgi_params;
+ proxy_http_version 1.1;
+ proxy_set_header Connection "";
+ }
+```
+
+AN example of a stream access is as follows:
+```
+vandal> curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE
+data: 2018-11-04T14:47:11.373124faultEthernet0major
+
+data: 2018-11-04T14:47:16.375265faultEthernet0major
+```
+
+You can also specify start and stop time. Start-time enables replay of existing samples, while stop-time is used both for replay, but also for stopping a stream at some future time.
+```
+ curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE?start-time=2014-10-25T10:02:00&stop-time=2014-10-25T12:31:00
+```
+
+See (stream tests)[../test/test_streams.sh] for more examples.
+
+### 3. Nchan
+
+As an alternative streams implementation, Nginx/Nchan can be used.
+Nginx uses pub/sub channels and can be configured in a variety of
+ways. The following uses a simple variant with one generic subscription
+channel (streams) and one publication channel (pub).
+
+The advantage with Nchan is the large eco-system around Nginx and Nchan.
+
+Native mode and Nchan mode can co-exist, but the publish URL of Nchan should be different from the streams URL of the native streams.
+
+Nchan mode does not use Clixon retention, since it uses its own replay mechanism.
+
+Download and install nchan, see (https://nchan.io/#install).
+
+Add the following to extend the Nginx configuration file with the following statements (example):
+```
+ location ~ /sub/(\w+)$ {
+ nchan_subscriber;
+ nchan_channel_id $1; #first capture of the location match
+ }
+ location ~ /pub/(\w+)$ {
+ nchan_publisher;
+ nchan_channel_id $1; #first capture of the location match
+ }
+```
+
+Configure clixon with `--enable-publish` which enables curl code for
+publishing streams to nchan.
+
+You also need to configure CLICON_STREAM_PUB to enable pushing notifications to Nginx/Nchan. Example:
+```
+http://localhost/pub
+```
+Clicon will then publish events from stream EXAMPLE to `http://localhost/pub/EXAMPLE
+
+Access the event stream EXAMPLE using curl:
+```
+ curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE
+: hi
+
+id: 1541344320:0
+data: 2018-11-04T15:12:00.435769faultEthernet0major
+
+id: 1541344325:0
+data: 2018-11-04T15:12:05.446425faultEthernet0major
+
+```
+Note that the SSE stream output is different than for native streams, and that `Last-Event-Id` is used for replay:
+```
+curl -H "Accept: text/event-stream" -H "Last-Event-ID: 1539961709:0" -s -X GET http://localhost/streams/EXAMPLE
+```
+
+See (https://nchan.io/#eventsource) on more info on how to access an SSE sub endpoint.
+
+### 4. Debugging
Start the restconf fastcgi program with debug flag:
```
-sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/example.xml" -s /bin/sh www-data
+sudo su -c "/www-data/clixon_restconf -D 1 -f /usr/local/etc/example.xml" -s /bin/sh www-data
```
Look at syslog:
```
@@ -77,3 +195,13 @@ Send command:
```
curl -G http://127.0.0.1/restconf/data/*
```
+
+You can also run restconf in a debugger.
+```
+sudo gdb /www-data/clixon_restconf
+(gdb) run -D 1 -f /usr/local/etc/example.xml
+```
+but you need to ensure /www-data/fastcgi_restconf.sock has the following access:
+```
+rwxr-xr-x 1 www-data www-data 0 sep 22 11:46 /www-data/fastcgi_restconf.sock
+```
\ No newline at end of file
diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c
index ab5db7b5..75c06699 100644
--- a/apps/restconf/restconf_lib.c
+++ b/apps/restconf/restconf_lib.c
@@ -54,7 +54,7 @@
/* clicon */
#include
-#include /* Need to be after clixon_xml-h due to attribute format */
+#include /* Need to be after clixon_xml-h due to attribute format */
#include "restconf_lib.h"
@@ -221,6 +221,26 @@ notfound(FCGX_Request *r)
return 0;
}
+/*! HTTP error 406 Not acceptable
+ * @param[in] r Fastcgi request handle
+ */
+int
+notacceptable(FCGX_Request *r)
+{
+ char *path;
+
+ clicon_debug(1, "%s", __FUNCTION__);
+ path = FCGX_GetParam("DOCUMENT_URI", r->envp);
+ FCGX_FPrintF(r->out, "Status: 406\r\n"); /* 406 not acceptible */
+
+ FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
+ FCGX_FPrintF(r->out, "Not Acceptable
\n");
+ FCGX_FPrintF(r->out, "Not Acceptable\n");
+ FCGX_FPrintF(r->out, "The target resource does not have a current representation that would be acceptable to the user agent.\n",
+ path);
+ return 0;
+}
+
/*! HTTP error 409
* @param[in] r Fastcgi request handle
*/
@@ -465,3 +485,23 @@ api_return_err(clicon_handle h,
cbuf_free(cb);
return retval;
}
+
+int
+restconf_terminate(clicon_handle h)
+{
+ yang_spec *yspec;
+ cxobj *x;
+
+ clixon_plugin_exit(h);
+ rpc_callback_delete_all();
+ clicon_rpc_close_session(h);
+ if ((yspec = clicon_dbspec_yang(h)) != NULL)
+ yspec_free(yspec);
+ if ((yspec = clicon_config_yang(h)) != NULL)
+ yspec_free(yspec);
+ if ((x = clicon_conf_xml(h)) != NULL)
+ xml_free(x);
+ clicon_handle_exit(h);
+ clicon_log_exit();
+ return 0;
+}
diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h
index cf8ef66b..e05eb054 100644
--- a/apps/restconf/restconf_lib.h
+++ b/apps/restconf/restconf_lib.h
@@ -36,6 +36,11 @@
#ifndef _RESTCONF_LIB_H_
#define _RESTCONF_LIB_H_
+/*
+ * Constants
+ */
+#define RESTCONF_API "restconf"
+
/*
* Prototypes (also in clixon_restconf.h)
*/
@@ -46,6 +51,7 @@ int badrequest(FCGX_Request *r);
int unauthorized(FCGX_Request *r);
int forbidden(FCGX_Request *r);
int notfound(FCGX_Request *r);
+int notacceptable(FCGX_Request *r);
int conflict(FCGX_Request *r);
int internal_server_error(FCGX_Request *r);
int notimplemented(FCGX_Request *r);
@@ -56,5 +62,6 @@ cbuf *readdata(FCGX_Request *r);
int get_user_cookie(char *cookiestr, char *attribute, char **val);
int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr,
int pretty, int use_xml);
+int restconf_terminate(clicon_handle h);
#endif /* _RESTCONF_LIB_H_ */
diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c
index 7ffc9260..ed7fdc43 100644
--- a/apps/restconf/restconf_main.c
+++ b/apps/restconf/restconf_main.c
@@ -34,18 +34,24 @@
*/
/*
+ * This program should be run as user www-data
+ *
* See draft-ietf-netconf-restconf-13.txt [draft]
* sudo apt-get install libfcgi-dev
* gcc -o fastcgi fastcgi.c -lfcgi
- * sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/example.xml " -s /bin/sh www-data
+ * sudo su -c "/www-data/clixon_restconf -D 1 -f /usr/local/etc/example.xml " -s /bin/sh www-data
* This is the interface:
* api/data/profile=/metric= PUT data:enable=
* api/test
*/
+#ifdef HAVE_CONFIG_H
+#include "clixon_config.h" /* generated by config & autoconf */
+#endif
+
#include
#include
#include
@@ -67,14 +73,15 @@
/* clicon */
#include
-#include /* Need to be after clixon_xml-h due to attribute format */
+#include /* Need to be after clixon_xml.h due to attribute format */
/* restconf */
#include "restconf_lib.h"
#include "restconf_methods.h"
+#include "restconf_stream.h"
/* Command line options to be passed to getopt(3) */
-#define RESTCONF_OPTS "hDf:p:y:"
+#define RESTCONF_OPTS "hD:f:l:p:y:a:u:"
/* RESTCONF enables deployments to specify where the RESTCONF API is
located. The client discovers this by getting the "/.well-known/host-meta"
@@ -82,9 +89,6 @@
*/
#define RESTCONF_WELL_KNOWN "/.well-known/host-meta"
-#define RESTCONF_API "restconf"
-#define RESTCONF_API_ROOT "/restconf"
-
/*! Generic REST method, GET, PUT, DELETE, etc
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
@@ -262,6 +266,7 @@ api_yang_library_version(clicon_handle h,
cxobj *xt = NULL;
cbuf *cb = NULL;
int pretty;
+ char *ietf_yang_library_revision = "2016-06-21"; /* XXX */
clicon_debug(1, "%s", __FUNCTION__);
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
@@ -271,7 +276,7 @@ api_yang_library_version(clicon_handle h,
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
FCGX_FPrintF(r->out, "\r\n");
- if (xml_parse_string("2016-06-21", NULL, &xt) < 0)
+ if (xml_parse_va(&xt, NULL, "%s", ietf_yang_library_revision) < 0)
goto done;
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
@@ -438,21 +443,8 @@ api_restconf(clicon_handle h,
return retval;
}
-static int
-restconf_terminate(clicon_handle h)
-{
- yang_spec *yspec;
- clixon_plugin_exit(h);
- rpc_callback_delete_all();
- clicon_rpc_close_session(h);
- if ((yspec = clicon_dbspec_yang(h)) != NULL)
- yspec_free(yspec);
- clicon_handle_exit(h);
- return 0;
-}
-
-/* Need global variable to for signal handler */
+/* Need global variable to for signal handler XXX */
static clicon_handle _CLICON_HANDLE = NULL;
/*! Signall terminates process
@@ -467,12 +459,24 @@ restconf_sig_term(int arg)
__PROGRAM__, __FUNCTION__, getpid(), arg);
else
exit(-1);
- if (_CLICON_HANDLE)
+ if (_CLICON_HANDLE){
+ stream_child_freeall(_CLICON_HANDLE);
restconf_terminate(_CLICON_HANDLE);
+ }
clicon_exit_set(); /* checked in event_loop() */
exit(-1);
}
+static void
+restconf_sig_child(int arg)
+{
+ int status;
+ int pid;
+
+ if ((pid = waitpid(-1, &status, 0)) != -1 && WIFEXITED(status))
+ stream_child_free(_CLICON_HANDLE, pid);
+}
+
/*! Usage help routine
* @param[in] argv0 command line
* @param[in] h Clicon handle
@@ -485,10 +489,13 @@ usage(clicon_handle h,
fprintf(stderr, "usage:%s [options]\n"
"where options are\n"
"\t-h \t\tHelp\n"
- "\t-D \t\tDebug. Log to syslog\n"
+ "\t-D \tDebug level\n"
"\t-f \tConfiguration file (mandatory)\n"
+ "\t-l > \tLog on (s)yslog, (f)ile (syslog is default)\n"
"\t-d \tSpecify restconf plugin directory dir (default: %s)\n"
- "\t-y \tOverride yang spec file (dont include .yang suffix)\n",
+ "\t-y \tLoad yang spec file (override yang main module)\n"
+ "\t-a UNIX|IPv4|IPv6\tInternal backend socket family\n"
+ "\t-u \tInternal socket domain path or IP addr (see -a)\n",
argv0,
clicon_restconf_dir(h)
);
@@ -513,12 +520,20 @@ main(int argc,
char *yangspec=NULL;
char *dir;
char *tmp;
+ int logdst = CLICON_LOG_SYSLOG;
+ yang_spec *yspec = NULL;
+ yang_spec *yspecfg = NULL; /* For config XXX clixon bug */
+ char *yang_filename = NULL;
+ char *stream_path;
+ int finish;
/* In the startup, logs to stderr & debug flag set later */
- clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_SYSLOG);
+ 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) {
@@ -526,29 +541,28 @@ main(int argc,
usage(h, argv[0]);
break;
case 'D' : /* debug */
- debug = 1;
+ if (sscanf(optarg, "%d", &debug) != 1)
+ usage(h, argv[0]);
break;
case 'f': /* override config file */
if (!strlen(optarg))
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
break;
- case 'd': /* Plugin directory */
- if (!strlen(optarg))
+ case 'l': /* Log destination: s|e|o */
+ if ((logdst = clicon_log_opt(optarg[0])) < 0)
usage(h, argv[0]);
- clicon_option_str_set(h, "CLICON_RESTCONF_DIR", optarg);
- break;
- case 'y' : /* yang module */
- yangspec = optarg;
- break;
- default:
- usage(h, argv[0]);
- break;
- }
- argc -= optind;
- argv += optind;
+ if (logdst == CLICON_LOG_FILE &&
+ strlen(optarg)>1 &&
+ clicon_log_file(optarg+1) < 0)
+ goto done;
+ break;
+ } /* switch getopt */
+ /*
+ * Logs, error and debug to stderr or syslog, set debug level
+ */
+ clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, logdst);
- clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_SYSLOG);
clicon_debug_init(debug, NULL);
clicon_log(LOG_NOTICE, "%s: %u Started", __PROGRAM__, getpid());
if (set_signal(SIGTERM, restconf_sig_term, NULL) < 0){
@@ -559,10 +573,51 @@ main(int argc,
clicon_err(OE_DEMON, errno, "Setting signal");
goto done;
}
-
- /* Find and read configfile */
- if (clicon_options_main(h) < 0)
+ if (set_signal(SIGCHLD, restconf_sig_child, NULL) < 0){
+ clicon_err(OE_DEMON, errno, "Setting signal");
goto done;
+ }
+
+ /* Create configure yang-spec */
+ if ((yspecfg = yspec_new()) == NULL)
+ goto done;
+ /* Find and read configfile */
+ if (clicon_options_main(h, yspecfg) < 0)
+ goto done;
+ clicon_config_yang_set(h, yspecfg);
+ stream_path = clicon_option_str(h, "CLICON_STREAM_PATH");
+ /* Now rest of options, some overwrite option file */
+ optind = 1;
+ opterr = 0;
+ while ((c = getopt(argc, argv, RESTCONF_OPTS)) != -1)
+ switch (c) {
+ case 'h' : /* help */
+ case 'D' : /* debug */
+ case 'f': /* config file */
+ case 'l': /* log */
+ break; /* see above */
+ case 'd': /* Plugin directory */
+ if (!strlen(optarg))
+ usage(h, argv[0]);
+ clicon_option_str_set(h, "CLICON_RESTCONF_DIR", optarg);
+ break;
+ case 'y' : /* Load yang spec file (override yang main module) */
+ yang_filename = optarg;
+ break;
+ case 'a': /* internal backend socket address family */
+ clicon_option_str_set(h, "CLICON_SOCK_FAMILY", optarg);
+ break;
+ case 'u': /* internal backend socket unix domain path or ip host */
+ if (!strlen(optarg))
+ usage(h, argv[0]);
+ clicon_option_str_set(h, "CLICON_SOCK", optarg);
+ break;
+ default:
+ usage(h, argv[0]);
+ break;
+ }
+ argc -= optind;
+ argv += optind;
/* Overwrite yang module with -y option */
if (yangspec)
@@ -573,10 +628,32 @@ main(int argc,
if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
return -1;
- /* Parse yang database spec file */
- if (yang_spec_main(h) == NULL)
+ /* Create top-level yang spec and store as option */
+ if ((yspec = yspec_new()) == NULL)
+ goto done;
+ clicon_dbspec_yang_set(h, yspec);
+ /* Load main application yang specification either module or specific file
+ * If -y is given, it overrides main module */
+ if (yang_filename){
+ if (yang_spec_parse_file(h, yang_filename, clicon_yang_dir(h), yspec, NULL) < 0)
+ goto done;
+ }
+ else if (yang_spec_parse_module(h, clicon_yang_module_main(h),
+ clicon_yang_dir(h),
+ clicon_yang_module_revision(h),
+ yspec, NULL) < 0)
goto done;
+ /* Load yang module library, RFC7895 */
+ if (yang_modules_init(h) < 0)
+ goto done;
+ /* Add system modules */
+ if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") &&
+ yang_spec_parse_module(h, "ietf-restconf-monitoring", CLIXON_DATADIR, NULL, yspec, NULL)< 0)
+ goto done;
+ if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") &&
+ yang_spec_parse_module(h, "ietf-netconf-notification", CLIXON_DATADIR, NULL, yspec, NULL)< 0)
+ goto done;
/* Call start function in all plugins before we go interactive
Pass all args after the standard options to plugin_start
*/
@@ -589,29 +666,34 @@ main(int argc,
clicon_err(OE_CFG, errno, "No CLICON_RESTCONF_PATH in clixon configure file");
goto done;
}
- if (FCGX_Init() != 0){
+ if (FCGX_Init() != 0){ /* How to cleanup memory after this? */
clicon_err(OE_CFG, errno, "FCGX_Init");
goto done;
}
+ clicon_debug(1, "restconf_main: Opening FCGX socket: %s", sockpath);
if ((sock = FCGX_OpenSocket(sockpath, 10)) < 0){
clicon_err(OE_CFG, errno, "FCGX_OpenSocket");
goto done;
}
-
if (FCGX_InitRequest(r, sock, 0) != 0){
clicon_err(OE_CFG, errno, "FCGX_InitRequest");
goto done;
}
while (1) {
+ finish = 1; /* If zero, dont finish request, initiate new */
+
if (FCGX_Accept_r(r) < 0) {
clicon_err(OE_CFG, errno, "FCGX_Accept_r");
goto done;
}
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)
+ clicon_debug(1, "path: %s", path);
+ if (strncmp(path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0)
api_restconf(h, r); /* This is the function */
+ else if (strncmp(path+1, stream_path, strlen(stream_path)) == 0) {
+ api_stream(h, r, stream_path, &finish);
+ }
else if (strncmp(path, RESTCONF_WELL_KNOWN, strlen(RESTCONF_WELL_KNOWN)) == 0) {
api_well_known(h, r); /* */
}
@@ -622,10 +704,19 @@ main(int argc,
}
else
clicon_debug(1, "NULL URI");
- FCGX_Finish_r(r);
+ if (finish)
+ FCGX_Finish_r(r);
+ else{ /* A handler is forked so we initiate a new request after instead
+ of finnishing the old */
+ if (FCGX_InitRequest(r, sock, 0) != 0){
+ clicon_err(OE_CFG, errno, "FCGX_InitRequest");
+ goto done;
+ }
+ }
}
retval = 0;
done:
+ stream_child_freeall(h);
restconf_terminate(h);
return retval;
}
diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c
index d75dafd9..133f0ed9 100644
--- a/apps/restconf/restconf_methods.c
+++ b/apps/restconf/restconf_methods.c
@@ -39,7 +39,7 @@
* sudo apt-get install libfcgi-dev
* gcc -o fastcgi fastcgi.c -lfcgi
- * sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/example.xml " -s /bin/sh www-data
+ * sudo su -c "/www-data/clixon_restconf -D 1 f /usr/local/etc/example.xml " -s /bin/sh www-data
* This is the interface:
* api/data/profile=/metric= PUT data:enable=
@@ -118,7 +118,7 @@ Mapping netconf error-tag -> status code
/* clicon */
#include
-#include /* Need to be after clixon_xml-h due to attribute format */
+#include /* Need to be after clixon_xml-h due to attribute format */
#include "restconf_lib.h"
#include "restconf_methods.h"
diff --git a/apps/restconf/restconf_stream.c b/apps/restconf/restconf_stream.c
new file mode 100644
index 00000000..08424521
--- /dev/null
+++ b/apps/restconf/restconf_stream.c
@@ -0,0 +1,507 @@
+/*
+ *
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLIXON.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ Alternatively, the contents of this file may be used under the terms of
+ the GNU General Public License Version 3 or later (the "GPL"),
+ in which case the provisions of the GPL are applicable instead
+ of those above. If you wish to allow use of your version of this file only
+ under the terms of the GPL, and not to allow others to
+ use your version of this file under the terms of Apache License version 2,
+ indicate your decision by deleting the provisions above and replace them with
+ the notice and other provisions required by the GPL. If you do not delete
+ the provisions above, a recipient may use your version of this file under
+ the terms of any one of the Apache License version 2 or the GPL.
+
+ ***** END LICENSE BLOCK *****
+
+ Restconf event stream implementation.
+ See RFC 8040 RESTCONF Protocol
+ Sections 3.8, 6, 9.3
+
+ RFC8040:
+ A RESTCONF server MAY send the "retry" field, and if it does, RESTCONF
+ clients SHOULD use it. A RESTCONF server SHOULD NOT send the "event"
+ or "id" fields, as there are no meaningful values. RESTCONF
+ servers that do not send the "id" field also do not need to support
+ the HTTP header field "Last-Event-ID"
+
+ The RESTCONF client can then use this URL value to start monitoring
+ the event stream:
+
+ GET /streams/NETCONF HTTP/1.1
+ Host: example.com
+ Accept: text/event-stream
+ Cache-Control: no-cache
+ Connection: keep-alive
+
+ The server MAY support the "start-time", "stop-time", and "filter"
+ query parameters, defined in Section 4.8. Refer to Appendix B.3.6
+ for filter parameter examples.
+
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "clixon_config.h" /* generated by config & autoconf */
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+/* cligen */
+#include
+
+/* clicon */
+#include
+
+#include /* Need to be after clixon_xml.h due to attribute format */
+
+#include "restconf_lib.h"
+#include "restconf_stream.h"
+
+/*
+ * Constants
+ */
+/* Enable for forking stream subscription loop.
+ * Disable to get single threading but blocking on streams
+ */
+#define STREAM_FORK 1
+
+/* Keep track of children - whjen they exit - their FCGX handle needs to be
+ * freed with FCGX_Free(&rbk, 0);
+ */
+struct stream_child{
+ qelem_t sc_q; /* queue header */
+ int sc_pid; /* Child process id */
+ FCGX_Request sc_r; /* FCGI stream data */
+};
+/* Linked list of children
+ * @note could hang STREAM_CHILD list on clicon handle instead.
+ */
+static struct stream_child *STREAM_CHILD = NULL;
+
+/*! Find restconf child using PID and cleanup FCGI Request data
+ * @param[in] h Clicon handle
+ * @param[in] pid Process id of child
+ * @note could hang STREAM_CHILD list on clicon handle instead.
+ */
+int
+stream_child_free(clicon_handle h,
+ int pid)
+{
+ struct stream_child *sc;
+
+ if ((sc = STREAM_CHILD) != NULL){
+ do {
+ if (pid == sc->sc_pid){
+ DELQ(sc, STREAM_CHILD, struct stream_child *);
+ FCGX_Free(&sc->sc_r, 0);
+ free(sc);
+ goto done;
+ }
+ sc = NEXTQ(struct stream_child *, sc);
+ } while (sc && sc != STREAM_CHILD);
+ }
+ done:
+ return 0;
+}
+
+int
+stream_child_freeall(clicon_handle h)
+{
+ struct stream_child *sc;
+
+ while ((sc = STREAM_CHILD) != NULL){
+ DELQ(sc, STREAM_CHILD, struct stream_child *);
+ FCGX_Free(&sc->sc_r, 1);
+ free(sc);
+ }
+ return 0;
+}
+
+/*! Callback when stream notifications arrive from backend
+ */
+static int
+restconf_stream_cb(int s,
+ void *arg)
+{
+ int retval = -1;
+ FCGX_Request *r = (FCGX_Request *)arg;
+ int eof;
+ struct clicon_msg *reply = NULL;
+ cxobj *xtop = NULL; /* top xml */
+ cxobj *xn; /* notification xml */
+ cbuf *cb = NULL;
+ int pretty = 0; /* XXX should be via arg */
+
+ clicon_debug(1, "%s", __FUNCTION__);
+ /* get msg (this is the reason this function is called) */
+ if (clicon_msg_rcv(s, &reply, &eof) < 0){
+ clicon_debug(1, "%s msg_rcv error", __FUNCTION__);
+ goto done;
+ }
+ clicon_debug(1, "%s msg: %s", __FUNCTION__, reply?reply->op_body:"null");
+ /* handle close from remote end: this will exit the client */
+ if (eof){
+ clicon_debug(1, "%s eof", __FUNCTION__);
+ clicon_err(OE_PROTO, ESHUTDOWN, "Socket unexpected close");
+ errno = ESHUTDOWN;
+ FCGX_FPrintF(r->out, "SHUTDOWN\r\n");
+ FCGX_FPrintF(r->out, "\r\n");
+ FCGX_FFlush(r->out);
+ clicon_exit_set();
+ goto done;
+ }
+ if (clicon_msg_decode(reply, &xtop) < 0)
+ goto done;
+ /* create event */
+ if ((cb = cbuf_new()) == NULL){
+ clicon_err(OE_PLUGIN, errno, "cbuf_new");
+ goto done;
+ }
+ if ((xn = xpath_first(xtop, "notification")) == NULL)
+ goto ok;
+#ifdef notused
+ xt = xpath_first(xn, "eventTime");
+ if ((xe = xpath_first(xn, "event")) == NULL) /* event can depend on yang? */
+ goto ok;
+
+ if (xt)
+ FCGX_FPrintF(r->out, "M#id: %s\r\n", xml_body(xt));
+ else{ /* XXX */
+ gettimeofday(&tv, NULL);
+ FCGX_FPrintF(r->out, "M#id: %02d:0\r\n", tv.tv_sec);
+ }
+#endif
+ if (clicon_xml2cbuf(cb, xn, 0, pretty) < 0)
+ goto done;
+ FCGX_FPrintF(r->out, "data: %s\r\n", cbuf_get(cb));
+ FCGX_FPrintF(r->out, "\r\n");
+ FCGX_FFlush(r->out);
+ ok:
+ retval = 0;
+ done:
+ clicon_debug(1, "%s retval: %d", __FUNCTION__, retval);
+ if (xtop != NULL)
+ xml_free(xtop);
+ if (reply)
+ free(reply);
+ if (cb)
+ cbuf_free(cb);
+ return retval;
+}
+
+/*! Send subsctription to backend
+ * @param[in] h Clicon handle
+ * @param[in] r Fastcgi request handle
+ * @param[in] name Stream name
+ * @param[out] sp Socket -1 if not set
+ */
+static int
+restconf_stream(clicon_handle h,
+ FCGX_Request *r,
+ char *name,
+ cvec *qvec,
+ int pretty,
+ int use_xml,
+ int *sp)
+{
+ int retval = -1;
+ cxobj *xret = NULL;
+ cxobj *xe;
+ cbuf *cb = NULL;
+ int s; /* socket */
+ int i;
+ cg_var *cv;
+ char *vname;
+
+ clicon_debug(1, "%s", __FUNCTION__);
+ *sp = -1;
+ if ((cb = cbuf_new()) == NULL){
+ clicon_err(OE_XML, errno, "cbuf_new");
+ goto done;
+ }
+ cprintf(cb, "%s", name);
+ /* Print all fields */
+ for (i=0; i");
+ cv2cbuf(cv, cb);
+ cprintf(cb, "");
+ }
+ else if (strcmp(vname, "stop-time") == 0){
+ cprintf(cb, "");
+ cv2cbuf(cv, cb);
+ cprintf(cb, "");
+ }
+ }
+ cprintf(cb, "]]>]]>");
+ if (clicon_rpc_netconf(h, cbuf_get(cb), &xret, &s) < 0)
+ goto done;
+ if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){
+ if (api_return_err(h, r, xe, pretty, use_xml) < 0)
+ goto done;
+ goto ok;
+ }
+ /* Setting up stream */
+ FCGX_SetExitStatus(201, r->out); /* Created */
+ FCGX_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_FFlush(r->out);
+ *sp = s;
+ ok:
+ retval = 0;
+ done:
+ clicon_debug(1, "%s retval: %d", __FUNCTION__, retval);
+ if (xret)
+ xml_free(xret);
+ if (cb)
+ cbuf_free(cb);
+ return retval;
+}
+
+/* restconf */
+#include "restconf_lib.h"
+#include "restconf_stream.h"
+
+static int
+stream_checkuplink(int s,
+ void *arg)
+{
+ FCGX_Request *r = (FCGX_Request *)arg;
+ clicon_debug(1, "%s", __FUNCTION__);
+ if (FCGX_GetError(r->out) != 0){ /* break loop */
+ clicon_debug(1, "%s FCGX_GetError upstream", __FUNCTION__);
+ clicon_exit_set();
+ }
+ return 0;
+}
+
+int
+stream_timeout(int s,
+ void *arg)
+{
+ struct timeval t;
+ struct timeval t1;
+ FCGX_Request *r = (FCGX_Request *)arg;
+
+ clicon_debug(1, "%s", __FUNCTION__);
+ if (FCGX_GetError(r->out) != 0){ /* break loop */
+ clicon_debug(1, "%s FCGX_GetError upstream", __FUNCTION__);
+ clicon_exit_set();
+ }
+ else{
+ gettimeofday(&t, NULL);
+ t1.tv_sec = 1; t1.tv_usec = 0;
+ timeradd(&t, &t1, &t);
+ event_reg_timeout(t, stream_timeout, arg, "Stream timeout");
+ }
+ return 0;
+}
+
+/*! Process a FastCGI request
+ * @param[in] r Fastcgi request handle
+ */
+int
+api_stream(clicon_handle h,
+ FCGX_Request *r,
+ char *streampath,
+ int *finish)
+{
+ 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;
+ int pretty;
+ int use_xml = 1; /* default */
+ cbuf *cbret = NULL;
+ cxobj *xret = NULL;
+ cxobj *xerr;
+ int s=-1;
+#ifdef STREAM_FORK
+ int pid;
+ struct stream_child *sc;
+#endif
+
+ clicon_debug(1, "%s", __FUNCTION__);
+ path = FCGX_GetParam("DOCUMENT_URI", r->envp);
+ query = FCGX_GetParam("QUERY_STRING", r->envp);
+ pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
+ test(r, 1);
+ 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], streampath)){
+ retval = notfound(r);
+ goto done;
+ }
+
+ 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 (restconf_stream(h, r, method, qvec, pretty, use_xml, &s) < 0)
+ goto done;
+ if (s != -1){
+#ifdef STREAM_FORK
+ if ((pid = fork()) == 0){ /* child */
+ 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);
+#endif /* STREAM_FORK */
+ /* Listen to backend socket */
+ if (event_reg_fd(s,
+ restconf_stream_cb,
+ (void*)r,
+ "stream socket") < 0)
+ goto done;
+ if (event_reg_fd(r->listen_sock,
+ stream_checkuplink,
+ (void*)r,
+ "stream socket") < 0)
+ goto done;
+ /* Poll upstream errors */
+ stream_timeout(0, (void*)r);
+ /* Start loop */
+ event_loop();
+ close(s);
+ event_unreg_fd(s, restconf_stream_cb);
+ event_unreg_fd(r->listen_sock, restconf_stream_cb);
+ event_unreg_timeout(stream_timeout, (void*)r);
+ clicon_exit_reset();
+#ifdef STREAM_FORK
+ FCGX_Finish_r(r);
+ FCGX_Free(r, 0);
+ restconf_terminate(h);
+ exit(0);
+ }
+ /* parent */
+ /* Create stream_child struct and store pid and FCGI data, when child
+ * killed, call FCGX_Free
+ */
+ if ((sc = malloc(sizeof(struct stream_child))) == NULL){
+ clicon_err(OE_XML, errno, "malloc");
+ goto done;
+ }
+ memset(sc, 0, sizeof(struct stream_child));
+ sc->sc_pid = pid;
+ sc->sc_r = *r;
+ ADDQ(sc, STREAM_CHILD);
+ *finish = 0; /* If spawn child, we should not finish this stream */
+#endif /* STREAM_FORK */
+ }
+ ok:
+ retval = 0;
+ 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/lib/clixon/clixon_xsl.h b/apps/restconf/restconf_stream.h
similarity index 77%
rename from lib/clixon/clixon_xsl.h
rename to apps/restconf/restconf_stream.h
index 5d572528..480832f1 100644
--- a/lib/clixon/clixon_xsl.h
+++ b/apps/restconf/restconf_stream.h
@@ -30,21 +30,17 @@
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
-
- * XML XPATH and XSLT functions.
+
*/
-#ifndef _CLIXON_XSL_H
-#define _CLIXON_XSL_H
+
+#ifndef _RESTCONF_STREAM_H_
+#define _RESTCONF_STREAM_H_
/*
* Prototypes
*/
-int xpath_vec_xsl(cxobj *cxtop, char *xpath, cxobj ***vec, size_t *veclen);
-int xpath_vec_flag_xsl(cxobj *cxtop, char *xpath, uint16_t flags,
- cxobj ***vec, size_t *veclen);
-cxobj *xpath_first_xsl(cxobj *cxtop, char *xpath);
-#ifdef COMPAT_XSL
-cxobj *xpath_each(cxobj *xn_top, char *xpath, cxobj *prev);
-#endif
+int stream_child_free(clicon_handle h, int pid);
+int stream_child_freeall(clicon_handle h);
+int api_stream(clicon_handle h, FCGX_Request *r, char *streampath, int *finish);
-#endif /* _CLIXON_XSL_H */
+#endif /* _RESTCONF_STREAM_H_ */
diff --git a/configure b/configure
index a6e6a366..c43ccbcb 100755
--- a/configure
+++ b/configure
@@ -633,9 +633,12 @@ CPP
OBJEXT
EXEEXT
ac_ct_CC
+wwwuser
+wwwdir
with_restconf
RANLIB
SH_SUFFIX
+CLIXON_DEFAULT_CONFIG
INSTALLFLAGS
INSTALL
INSTALL_DATA
@@ -683,7 +686,6 @@ infodir
docdir
oldincludedir
includedir
-runstatedir
localstatedir
sharedstatedir
sysconfdir
@@ -708,6 +710,7 @@ ac_user_opts='
enable_option_checking
enable_debug
with_cligen
+enable_publish
with_restconf
with_configfile
'
@@ -760,7 +763,6 @@ datadir='${datarootdir}'
sysconfdir='${prefix}/etc'
sharedstatedir='${prefix}/com'
localstatedir='${prefix}/var'
-runstatedir='${localstatedir}/run'
includedir='${prefix}/include'
oldincludedir='/usr/include'
docdir='${datarootdir}/doc/${PACKAGE}'
@@ -1013,15 +1015,6 @@ do
| -silent | --silent | --silen | --sile | --sil)
silent=yes ;;
- -runstatedir | --runstatedir | --runstatedi | --runstated \
- | --runstate | --runstat | --runsta | --runst | --runs \
- | --run | --ru | --r)
- ac_prev=runstatedir ;;
- -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
- | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
- | --run=* | --ru=* | --r=*)
- runstatedir=$ac_optarg ;;
-
-sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
ac_prev=sbindir ;;
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
@@ -1159,7 +1152,7 @@ fi
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
datadir sysconfdir sharedstatedir localstatedir includedir \
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
- libdir localedir mandir runstatedir
+ libdir localedir mandir
do
eval ac_val=\$$ac_var
# Remove trailing slashes.
@@ -1312,7 +1305,6 @@ Fine tuning of the installation directories:
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
- --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
--libdir=DIR object code libraries [EPREFIX/lib]
--includedir=DIR C header files [PREFIX/include]
--oldincludedir=DIR C header files for non-gcc [/usr/include]
@@ -1346,6 +1338,8 @@ Optional Features:
--disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
--enable-FEATURE[=ARG] include FEATURE [ARG=yes]
--enable-debug Build with debug symbols, default: no
+ --enable-publish Enable publish of notification streams using SSE and
+ curl
Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
@@ -2155,9 +2149,9 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
: ${INSTALLFLAGS="-s"}
CLIXON_VERSION_MAJOR="3"
-CLIXON_VERSION_MINOR="7"
+CLIXON_VERSION_MINOR="8"
CLIXON_VERSION_PATCH="0"
-CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\""
+CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}.PRE\""
# Fix to specific CLIgen version (eg 3.5) or head (3)
CLIGEN_VERSION="3"
@@ -2192,6 +2186,7 @@ _ACEOF
+# AC_SUBST(var) makes @var@ appear in makefiles.
# clixon versions spread to Makefile's (.so files) and variable in build.c
@@ -2448,7 +2443,12 @@ test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
+
# If yes, compile apps/restconf
+wwwdir=/www-data
+
+wwwuser=www-data
+
#
ac_ext=c
ac_cpp='$CPP $CPPFLAGS'
@@ -3679,6 +3679,77 @@ if test "${with_cligen}"; then
test -d "$with_cligen" && CLIGEN_PREFIX="$with_cligen"
fi
+# Experimental: Curl publish notification stream to eg Nginx nchan.
+# Check whether --enable-publish was given.
+if test "${enable_publish+set}" = set; then :
+ enableval=$enable_publish;
+ if test "$enableval" = no; then
+ ac_enable_publish=no
+ else
+ ac_enable_publish=yes
+ fi
+
+else
+ ac_enable_publish=no
+fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: publish is $ac_enable_publish" >&5
+$as_echo "publish is $ac_enable_publish" >&6; }
+
+if test "$ac_enable_publish" = "yes"; then
+ # publish streams uses libcurl
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl_global_init in -lcurl" >&5
+$as_echo_n "checking for curl_global_init in -lcurl... " >&6; }
+if ${ac_cv_lib_curl_curl_global_init+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lcurl $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char curl_global_init ();
+int
+main ()
+{
+return curl_global_init ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_curl_curl_global_init=yes
+else
+ ac_cv_lib_curl_curl_global_init=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_curl_curl_global_init" >&5
+$as_echo "$ac_cv_lib_curl_curl_global_init" >&6; }
+if test "x$ac_cv_lib_curl_curl_global_init" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBCURL 1
+_ACEOF
+
+ LIBS="-lcurl $LIBS"
+
+else
+ as_fn_error $? "libcurl missing" "$LINENO" 5
+fi
+
+
+$as_echo "#define CLIXON_PUBLISH_STREAMS 1" >>confdefs.h
+
+fi
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
$as_echo_n "checking for grep that handles long lines and -e... " >&6; }
@@ -4063,12 +4134,11 @@ fi
fi
# Set default config file location
+CLIXON_DEFAULT_CONFIG=/usr/local/etc/clixon.xml
# Check whether --with-configfile was given.
if test "${with_configfile+set}" = set; then :
- withval=$with_configfile; DEFAULT_CONFIG="$withval"
-else
- DEFAULT_CONFIG="$(eval echo ${sysconfdir}/clixon.xml)"
+ withval=$with_configfile; CLIXON_DEFAULT_CONFIG="$withval"
fi
@@ -4321,7 +4391,7 @@ _ACEOF
# Default location for config file
cat >>confdefs.h <<_ACEOF
-#define CLIXON_DEFAULT_CONFIG "${DEFAULT_CONFIG}"
+#define CLIXON_DEFAULT_CONFIG "${CLIXON_DEFAULT_CONFIG}"
_ACEOF
diff --git a/configure.ac b/configure.ac
index 964141c1..1a84825f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -43,9 +43,9 @@ AC_INIT(lib/clixon/clixon.h.in)
: ${INSTALLFLAGS="-s"}
CLIXON_VERSION_MAJOR="3"
-CLIXON_VERSION_MINOR="7"
+CLIXON_VERSION_MINOR="8"
CLIXON_VERSION_PATCH="0"
-CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\""
+CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}.PRE\""
# Fix to specific CLIgen version (eg 3.5) or head (3)
CLIGEN_VERSION="3"
@@ -63,6 +63,7 @@ AC_DEFINE_UNQUOTED(CLIXON_VERSION_MINOR, $CLIXON_VERSION_MINOR, [Clixon minor re
AC_DEFINE_UNQUOTED(CLIXON_VERSION_PATCH, $CLIXON_VERSION_PATCH, [Clixon path version])
+# AC_SUBST(var) makes @var@ appear in makefiles.
# clixon versions spread to Makefile's (.so files) and variable in build.c
AC_SUBST(CLIXON_VERSION)
AC_SUBST(CLIXON_VERSION_STRING)
@@ -85,10 +86,13 @@ AC_SUBST(INSTALL)
AC_SUBST(INSTALL_DATA)
AC_SUBST(INSTALL_PROGRAM)
AC_SUBST(INSTALLFLAGS)
+AC_SUBST(CLIXON_DEFAULT_CONFIG)
AC_SUBST(LIBS)
AC_SUBST(SH_SUFFIX)
AC_SUBST(RANLIB)
AC_SUBST(with_restconf) # If yes, compile apps/restconf
+AC_SUBST(wwwdir,/www-data)
+AC_SUBST(wwwuser,www-data)
#
AC_PROG_CC()
AC_PROG_CPP
@@ -141,6 +145,23 @@ if test "${with_cligen}"; then
test -d "$with_cligen" && CLIGEN_PREFIX="$with_cligen"
fi
+# Experimental: Curl publish notification stream to eg Nginx nchan.
+AC_ARG_ENABLE(publish, AS_HELP_STRING([--enable-publish],[Enable publish of notification streams using SSE and curl]),[
+ if test "$enableval" = no; then
+ ac_enable_publish=no
+ else
+ ac_enable_publish=yes
+ fi
+ ],
+ [ ac_enable_publish=no])
+AC_MSG_RESULT(publish is $ac_enable_publish)
+
+if test "$ac_enable_publish" = "yes"; then
+ # publish streams uses libcurl
+ AC_CHECK_LIB(curl, curl_global_init,, AC_MSG_ERROR([libcurl missing]))
+ AC_DEFINE(CLIXON_PUBLISH_STREAMS, 1, [Enable publish of notification streams using SSE and curl])
+fi
+
AC_CHECK_HEADERS(cligen/cligen.h,, AC_MSG_ERROR(cligen missing. Try: git clone https://github.com/olofhagsand/cligen.git))
AC_CHECK_LIB(cligen, cligen_init,, AC_MSG_ERROR([CLIgen${CLIGEN_VERSION} missing. Try: git clone https://github.com/olofhagsand/cligen.git]))
@@ -156,10 +177,10 @@ if test "x${with_restconf}" == xyes; then
fi
# Set default config file location
+CLIXON_DEFAULT_CONFIG=/usr/local/etc/clixon.xml
AC_ARG_WITH([configfile],
[AS_HELP_STRING([--with-configfile=FILE],[set default path to config file])],
- [DEFAULT_CONFIG="$withval"],
- [DEFAULT_CONFIG="$(eval echo ${sysconfdir}/clixon.xml)"])
+ [CLIXON_DEFAULT_CONFIG="$withval"],)
AC_CHECK_LIB(crypt, crypt)
AC_CHECK_HEADERS(crypt.h)
@@ -184,7 +205,7 @@ CLIXON_DATADIR="${prefix}/share/clixon"
AC_DEFINE_UNQUOTED(CLIXON_DATADIR, "${CLIXON_DATADIR}", [Clixon data dir for system yang files etc])
# Default location for config file
-AC_DEFINE_UNQUOTED(CLIXON_DEFAULT_CONFIG,"${DEFAULT_CONFIG}",[Location for apps to find default config file])
+AC_DEFINE_UNQUOTED(CLIXON_DEFAULT_CONFIG,"${CLIXON_DEFAULT_CONFIG}",[Location for apps to find default config file])
AH_BOTTOM([#include ])
diff --git a/datastore/datastore_client.c b/datastore/datastore_client.c
index bdcd4ac5..82c0263e 100644
--- a/datastore/datastore_client.c
+++ b/datastore/datastore_client.c
@@ -206,7 +206,7 @@ main(int argc, char **argv)
if ((yspec = yspec_new()) == NULL)
goto done;
/* Parse yang spec from given file */
- if (yang_parse(h, yangdir, yangmodule, NULL, yspec) < 0)
+ if (yang_parse(h, NULL, yangmodule, yangdir, NULL, yspec, NULL) < 0)
goto done;
/* Set database directory option */
if (xmldb_setopt(h, "dbdir", dbdir) < 0)
diff --git a/datastore/keyvalue/clixon_keyvalue.c b/datastore/keyvalue/clixon_keyvalue.c
index 4e77418d..39a11134 100644
--- a/datastore/keyvalue/clixon_keyvalue.c
+++ b/datastore/keyvalue/clixon_keyvalue.c
@@ -211,7 +211,7 @@ append_listkeys(cbuf *ckey,
xml_name(xt), keyname);
goto done;
}
- if (uri_percent_encode(xml_body(xkey), &bodyenc) < 0)
+ if (uri_percent_encode(&bodyenc, "%s", xml_body(xkey)) < 0)
goto done;
if (i++)
cprintf(ckey, ",");
@@ -328,7 +328,7 @@ get(char *dbname,
* If xml element is a leaf-list, then the next element is expected to
* be a value
*/
- if (uri_percent_decode(restval, &argdec) < 0)
+ if (uri_percent_decode(&argdec, restval) < 0)
goto done;
if ((xc = xml_find(x, name))==NULL ||
(xb = xml_find(xc, argdec))==NULL){
@@ -681,7 +681,7 @@ put(char *dbfile,
goto done;
break;
case Y_LEAF_LIST:
- if (uri_percent_encode(body, &bodyenc) < 0)
+ if (uri_percent_encode(&bodyenc, "%s", body) < 0)
goto done;
cprintf(cbxk, "=%s", bodyenc);
break;
diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c
index 7b7d52d8..a7059514 100644
--- a/datastore/text/clixon_xmldb_text.c
+++ b/datastore/text/clixon_xmldb_text.c
@@ -125,7 +125,7 @@ text_db2file(struct text_handle *th,
char **filename)
{
int retval = -1;
- cbuf *cb;
+ cbuf *cb = NULL;
char *dir;
if ((cb = cbuf_new()) == NULL){
@@ -174,7 +174,6 @@ text_connect(void)
xh = (xmldb_handle)th;
done:
return xh;
-
}
/*! Disconnect from to a datastore plugin and deallocate handle
@@ -191,6 +190,7 @@ text_disconnect(xmldb_handle xh)
size_t klen;
int i;
+ clicon_debug(1, "%s", __FUNCTION__);
if (th){
if (th->th_dbdir)
free(th->th_dbdir);
@@ -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;
}
@@ -1387,7 +1387,7 @@ main(int argc,
db_init(db);
if ((yspec = yspec_new()) == NULL)
goto done
- if (yang_parse(h, yangdir, yangmod, NULL, yspec) < 0)
+ if (yang_parse(h, NULL, yangmod, yangdir, NULL, yspec) < 0)
goto done;
if (strcmp(cmd, "get")==0){
if (argc < 5)
diff --git a/doc/FAQ.md b/doc/FAQ.md
index c1d130d0..25727e98 100644
--- a/doc/FAQ.md
+++ b/doc/FAQ.md
@@ -2,17 +2,18 @@
## What is Clixon?
-Clixon is a configuration management tool including a generated CLI ,
-Yang parser, netconf and restconf interface and an embedded databases.
+Clixon is a YANG-based configuration manager, with interactive CLI,
+NETCONF and RESTCONF interfaces, an embedded database and transaction
+support.
## Why should I use Clixon?
-If you want an easy-to-use configuration frontend based on yang with an
+If you want an easy-to-use configuration toolkit based on yang with an
open-source license. Typically for embedded devices requiring a
config interface such as routers and switches.
## What license is available?
-CLIXON is dual license. Either Apache License, Version 2.0 or GNU
+Clixon is dual license. Either Apache License, Version 2.0 or GNU
General Public License Version 2.
## Is Clixon extendible?
@@ -41,9 +42,19 @@ The example:
sudo make install
```
+## How do you run Clixon example commands?
+
+- Start a backend server: `clixon_backend -Ff /usr/local/etc/example.xml`
+- Start a cli session: `clixon_cli -f /usr/local/etc/example.xml`
+- Start a netconf session: `clixon_netconf -f /usr/local/etc/example.xml`
+- Start a restconf daemon: `sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data`
+- Send a restconf command: `curl -G http://127.0.0.1/restconf/data`
+
+More info in the [example](../example) directory.
+
## Do I need to setup anything? (IMPORTANT)
-The config demon requires a valid group to create a server UNIX socket.
+The config demon requires a valid group to create a server UNIX domain socket.
Define a valid CLICON_SOCK_GROUP in the config file or via the -g option
or create the group and add the user to it. The default group is 'clicon'.
Add yourself and www-data, if you intend to use restconf.
@@ -63,18 +74,11 @@ clicon:x:1001:,www-data
## What about reference documentation?
Clixon uses Doxygen for reference documentation.
-Build using 'make doc' and aim your browser at doc/html/index.html or
-use the web resource: http://clicon.org/ref/index.html
-
-## How do you run the example?
-- Start a backend server: 'clixon_backend -Ff /usr/local/etc/example.xml'
-- Start a cli session: clixon_cli -f /usr/local/etc/example.xml
-- Start a netconf session: clixon_netconf -f /usr/local/etc/example.xml
+Build using 'make doc' and aim your browser at doc/html/index.html.
## How is configuration data stored?
-Configuration data is stored in an XML datastore. The default is a
-text-based datastore. In the example the datastore are regular files found in
-/usr/local/var/example/.
+Configuration data is stored in an XML datastore. In the example the
+datastore are regular files found in /usr/local/var/example/.
## What is validate and commit?
Clixon follows netconf in its validate and commit semantics.
@@ -93,20 +97,36 @@ configuration file is /usr/local/etc/clixon.xml. The example
configuration file is installed at /usr/local/etc/example.xml. The
YANG specification for the configuration file is clixon-config.yang.
-You can change where CLixon looks for the configuration FILE as follows:
+You can change where Clixon looks for the configuration FILE as follows:
- Provide -f FILE option when starting a program (eg clixon_backend -f FILE)
- Provide --with-configfile=FILE when configuring
- Provide --with-sysconfig= when configuring, then FILE is /clixon.xml
- Provide --sysconfig= when configuring then FILE is /etc/clixon.xml
- FILE is /usr/local/etc/clixon.xml
+## How do I enable Yang features?
+
+Yang models have features, and parts of a specification can be
+conditional using the if-feature statement. In Clixon, features are
+enabled in the configuration file using .
+
+The example below shows enabling a specific feature; enabling all features in module; and enabling all features in all modules, respectively:
+```
+ ietf-routing:router-id
+ ietf-routing:*
+ *:*
+```
+
+Features can be probed by using RFC 7895 Yang module library which provides
+information on all modules and which features are enabled.
+
## Can I run Clixon as docker containers?
Yes, the example works as docker containers as well. There should be a
-prepared container in docker hib for the example where the backend and
+prepared container in docker hub for the example where the backend and
CLI is bundled.
```
-sudo docker run -ti --rm olofhagsand/clixon_example
+sudo docker run -td olofhagsand/clixon_example
```
Look in the example documentation for more info.
@@ -114,7 +134,9 @@ Look in the example documentation for more info.
As an alternative to cli configuration, you can use netconf. Easiest is to just pipe netconf commands to the clixon_netconf application.
Example:
+```
echo "]]>]]>" | clixon_netconf -f /usr/local/etc/example.xml
+```
However, more useful is to run clixon_netconf as an SSH
subsystem. Register the subsystem in /etc/sshd_config:
@@ -139,7 +161,6 @@ For example, using nginx, install, and edit config file: /etc/nginx/sites-availa
server {
...
location /restconf {
- root /usr/share/nginx/html/restconf;
fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
include fastcgi_params;
}
@@ -150,8 +171,6 @@ Start nginx daemon
sudo /etc/init.d/nginx start
```
-Read more in the restconf docs.
-
Example:
```
curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=eth9/type
@@ -161,24 +180,38 @@ Example:
}
]
```
+Read more in the (restconf)[../apps/restconf] docs.
-## How do I use notifications?
-The example has a prebuilt notification stream called "ROUTING" that triggers every 10s.
-You enable the notification either via the cli:
+## Does Clixon support event streams?
+
+Yes, Clixon supports event notification streams in the CLI, Netconf and Restconf API:s.
+
+The example has a prebuilt notification stream called "EXAMPLE" that triggers every 5s.
+You enable the notification via the CLI:
```
cli> notify
cli>
-```
-or via netconf:
-```
-clixon_netconf -qf /usr/local/etc/example.xml
-ROUTING]]>]]>
-]]>]]>
-Routing notification]]>]]>
-Routing notification]]>]]>
+event-class fault;
+reportingEntity {
+ card Ethernet0;
+}
+severity major;
...
```
+or via NETCONF:
+```
+clixon_netconf -qf /usr/local/etc/example.xml
+EXAMPLE]]>]]>
+]]>]]>
+2018-09-30T12:44:59.657276faultEthernet0major]]>]]>
+...
+```
+or via restconf:
+```
+ curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE
+```
+Consult (../apps/restconf/README.md) on more information on how to setup a reverse proxy for restconf streams. It is also possible to configure a pub/sub system such as (Nginx Nchan)[https://nchan.io].
## How should I start the backend daemon?
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 2a2ce179..6bdadc9c 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -40,16 +40,13 @@ RUN apt-get update && apt-get install -y \
libfcgi-dev \
libcurl4-openssl-dev
-
-RUN groupadd clicon
-
# Create a directory to hold source-code, dependencies etc
RUN mkdir /clixon
WORKDIR /clixon
# Clone cligen and clixon
RUN git clone https://github.com/olofhagsand/cligen.git
-RUN git clone https://github.com/clicon/clixon.git
+RUN git clone -b develop https://github.com/clicon/clixon.git
# Build cligen
WORKDIR /clixon/cligen
@@ -59,12 +56,13 @@ RUN make install
# Build clixon
WORKDIR /clixon/clixon
-RUN git checkout -b develop origin/develop
RUN ./configure
RUN make
RUN make install
RUN make install-include
+RUN rm -rf /clixon
+
RUN ldconfig
diff --git a/docker/Makefile.in b/docker/Makefile.in
index 58905f5b..23d0a7e8 100644
--- a/docker/Makefile.in
+++ b/docker/Makefile.in
@@ -47,7 +47,7 @@ SHELL = /bin/sh
.PHONY: all clean depend install docker push
all:
- @echo "Run make docker to build docker image"
+ echo "Run make docker to build docker image"
clean:
@@ -56,6 +56,7 @@ distclean: clean
docker:
sudo docker build -t $(IMAGE) .
+ echo "cd ../example; make docker to build example application"
push:
sudo docker push $(IMAGE)
diff --git a/docker/README.md b/docker/README.md
index 95c9af33..039eb07a 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -6,7 +6,7 @@ the IMAGE in Makefile.in and push it to another name.
The clixon docker image is a base image that can be used to build
clixon applications. It has all the whole code for a clixon release
-which it downloads from git - it does not use local code.
+which it downloads from git - it does not use local code (note it may even use develop branch).
See example/docker for how to build a docker application using the base image.
@@ -16,4 +16,4 @@ Perform the build by 'make docker'.
You may also do 'make push' if you want to push the image, but you may then consider changing the image name (in the makefile:s).
You may run the container directly by going directly to example and
-the docker runtime scripts there
\ No newline at end of file
+the docker runtime scripts there
diff --git a/example/Dockerfile b/example/Dockerfile
index 24403dc5..08314bf8 100644
--- a/example/Dockerfile
+++ b/example/Dockerfile
@@ -34,8 +34,11 @@
FROM olofhagsand/clixon
MAINTAINER Olof Hagsand
-RUN apt-get update && apt-get install -y \
- procps # ps for debugging
+#RUN apt-get update && apt-get install -y procps # ps for debugging
+
+# The example uses "clicon" group
+RUN groupadd clicon
+
# Create a directory to hold source-code, dependencies etc
RUN mkdir /example
WORKDIR /example
@@ -53,7 +56,8 @@ RUN make
RUN make install
RUN install example.xml /usr/local/etc/clixon.xml
-CMD /usr/local/sbin/clixon_backend && /usr/local/bin/clixon_cli
+# Log to stderr. Add -D 1 for debug
+CMD /usr/local/sbin/clixon_backend -F -a IPv4 -u 0.0.0.0 -s init -l e
diff --git a/example/Makefile.in b/example/Makefile.in
index c95ff1de..bbbd9070 100644
--- a/example/Makefile.in
+++ b/example/Makefile.in
@@ -43,6 +43,8 @@ localstatedir = @localstatedir@
libdir = @exec_prefix@/lib
APPNAME = example
+# Install here if you want default clixon location:
+CLIXON_DEFAULT_CONFIG = @CLIXON_DEFAULT_CONFIG@
CC = @CC@
CFLAGS = @CFLAGS@ -rdynamic -fPIC
@@ -56,6 +58,7 @@ CLI_PLUGIN = $(APPNAME)_cli.so
NETCONF_PLUGIN = $(APPNAME)_netconf.so
RESTCONF_PLUGIN = $(APPNAME)_restconf.so
+
# Example docker image. PLEASE CHANGE THIS
IMAGE = olofhagsand/clixon_example
@@ -120,6 +123,7 @@ distclean: clean
install: $(YANGSPECS) $(CLISPECS) $(BE_PLUGIN) $(BE2_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) $(RESTCONF_PLUGIN) $(APPNAME).xml
install -d -m 0755 $(DESTDIR)$(sysconfdir)
install -m 0644 $(APPNAME).xml $(DESTDIR)$(sysconfdir)
+# install -m 0644 $(APPNAME).xml $(DESTDIR)$(CLIXON_DEFAULT_CONFIG)
install -d -m 0755 $(DESTDIR)$(datarootdir)/$(APPNAME)/yang
install -m 0644 $(YANGSPECS) $(DESTDIR)$(datarootdir)/$(APPNAME)/yang
install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/cli
diff --git a/example/README.md b/example/README.md
index 0c6d6356..6fdc8641 100644
--- a/example/README.md
+++ b/example/README.md
@@ -41,7 +41,7 @@ Send netconf command:
```
Start clixon restconf daemon
```
-> sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data
+ sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data
```
Send restconf command
```
@@ -76,12 +76,12 @@ Send restconf command
]]>]]>
```
-## Creating notification
+## Streams
-The example has an example notification triggering every 10s. To start a notification
-stream in the session, create a subscription:
+The example has an EXAMPLE stream notification triggering every 5s. To start a notification
+stream in the session using netconf, create a subscription:
```
-ROUTING]]>]]>
+EXAMPLE]]>]]>
]]>]]>
Routing notification]]>]]>
Routing notification]]>]]>
@@ -95,6 +95,8 @@ Routing notification
...
```
+Restconf support is also supported, see [../apps/restconf/README.md].
+
## Initializing a plugin
The example includes a restonf, netconf, CLI and two backend plugins.
@@ -198,19 +200,13 @@ The example contains some stubs for authorization according to [RFC8341(NACM)](h
Example systemd files for backend and restconf daemons are found under the systemd directory. Install them under /etc/systemd/system for example.
-## Run as docker container
-
-(Note not updated)
-```
-cd docker
-# look in README
-```
-
## Docker
-Run the example as a docker container as follows:
+Run the example as a docker container and access it from a host CLI as follows:
```
-sudo docker run -ti --rm olofhagsand/clixon_example
+ID=$(sudo docker run -td olofhagsand/clixon_example)
+IP=$(sudo docker inspect -f '{{.NetworkSettings.IPAddress }}' $ID)
+clixon_cli -a IPv4 -u $IP -f ./example.xml
```
Build the container and push yourself: First change the IMAGE variable in Makefile (eg to "you/clixon_example). Then build and push:
@@ -220,5 +216,9 @@ make push
sudo docker run -ti --rm you/clixon_example
```
+Note that the configuration database is internal in the container, so
+it is deleted if the container is restarted. To make the configuration
+database persistent, you need to mount running_db using `-v`
+
diff --git a/example/example.xml b/example/example.xml
index 337048bb..c222f6ce 100644
--- a/example/example.xml
+++ b/example/example.xml
@@ -1,5 +1,6 @@
/usr/local/etc/example.xml
+ *:*
/usr/local/share/example/yang
example
example
diff --git a/example/example.yang b/example/example.yang
index e90bfdef..1bba52d3 100644
--- a/example/example.yang
+++ b/example/example.yang
@@ -22,12 +22,38 @@ module example {
base if:interface-type;
}
/* Translation function example - See also example_cli */
-
list translate{
leaf value{
type string;
}
}
+ /* State data (not config) for the example application*/
+ container state {
+ config false;
+ description "state data for the example application (must be here for example get operation)";
+ leaf-list op {
+ type string;
+ }
+ }
+ /* Example notification as used in RFC 5277 and RFC 8040 */
+ notification event {
+ description "Example notification event.";
+ leaf event-class {
+ type string;
+ description "Event class identifier.";
+ }
+ container reportingEntity {
+ description "Event specific information.";
+ leaf card {
+ type string;
+ description "Line card identifier.";
+ }
+ }
+ leaf severity {
+ type string;
+ description "Event severity description.";
+ }
+ }
rpc client-rpc {
description "Example local client-side RPC that is processed by the
the netconf/restconf and not sent to the backend.
diff --git a/example/example_backend.c b/example/example_backend.c
index 4db69f71..3a420a0e 100644
--- a/example/example_backend.c
+++ b/example/example_backend.c
@@ -31,17 +31,14 @@
***** END LICENSE BLOCK *****
- *
- * IETF yang routing example
*/
-
-
#include
#include
#include
#include
#include
#include
+#include
#include
/* clicon */
@@ -54,7 +51,7 @@
#include
/* forward */
-static int notification_timer_setup(clicon_handle h);
+static int example_stream_timer_setup(clicon_handle h);
/*! This is called on validate (and commit). Check validity of candidate
*/
@@ -93,32 +90,33 @@ transaction_commit(clicon_handle h,
/*! Routing example notifcation timer handler. Here is where the periodic action is
*/
static int
-notification_timer(int fd,
- void *arg)
+example_stream_timer(int fd,
+ void *arg)
{
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, "EXAMPLE", "faultEthernet0major") < 0)
goto done;
- if (notification_timer_setup(h) < 0)
+ if (example_stream_timer_setup(h) < 0)
goto done;
retval = 0;
done:
return retval;
}
-/*! Set up routing notifcation timer
+/*! Set up example stream notification timer
*/
static int
-notification_timer_setup(clicon_handle h)
+example_stream_timer_setup(clicon_handle h)
{
struct timeval t, t1;
gettimeofday(&t, NULL);
- t1.tv_sec = 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");
+ return event_reg_timeout(t, example_stream_timer, h, "example stream timer");
}
/*! IETF Routing fib-route rpc
@@ -176,8 +174,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 +192,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:
@@ -221,8 +227,8 @@ example_reset(clicon_handle h,
cxobj *xt = NULL;
if (xml_parse_string(""
- "loex:loopback"
- "", NULL, &xt) < 0)
+ "loex:loopback"
+ "", NULL, &xt) < 0)
goto done;
/* Replace parent w fiorst child */
if (xml_rootchild(xt, 0, &xt) < 0)
@@ -257,13 +263,19 @@ example_start(clicon_handle h,
return 0;
}
+int
+example_exit(clicon_handle h)
+{
+ return 0;
+}
+
clixon_plugin_api *clixon_plugin_init(clicon_handle h);
static clixon_plugin_api api = {
"example", /* name */
clixon_plugin_init, /* init - must be called clixon_plugin_init */
example_start, /* start */
- NULL, /* exit */
+ example_exit, /* exit */
.ca_reset=example_reset, /* reset */
.ca_statedata=example_statedata, /* statedata */
.ca_trans_begin=NULL, /* trans begin */
@@ -282,9 +294,27 @@ static clixon_plugin_api api = {
clixon_plugin_api *
clixon_plugin_init(clicon_handle h)
{
+ struct timeval retention = {0,0};
+
clicon_debug(1, "%s backend", __FUNCTION__);
- if (notification_timer_setup(h) < 0)
+ /* Example stream initialization:
+ * 1) Register EXAMPLE stream
+ * 2) setup timer for notifications, so something happens on stream
+ * 3) setup stream callbacks for notification to push channel
+ */
+ if (clicon_option_exists(h, "CLICON_STREAM_RETENTION"))
+ retention.tv_sec = clicon_option_int(h, "CLICON_STREAM_RETENTION");
+ if (stream_add(h, "EXAMPLE", "Example event stream", 1, &retention) < 0)
goto done;
+ /* Enable nchan pub/sub streams
+ * assumes: CLIXON_PUBLISH_STREAMS, eg configure --enable-publish
+ */
+ if (clicon_option_exists(h, "CLICON_STREAM_PUB") &&
+ stream_publish(h, "EXAMPLE") < 0)
+ goto done;
+ if (example_stream_timer_setup(h) < 0)
+ goto done;
+
/* Register callback for routing rpc calls */
if (rpc_callback_register(h, fib_route,
NULL,
@@ -301,6 +331,7 @@ clixon_plugin_init(clicon_handle h)
"empty"/* Xml tag when callback is made */
) < 0)
goto done;
+
/* Return plugin API */
return &api;
done:
diff --git a/example/example_backend_nacm.c b/example/example_backend_nacm.c
index f484362e..3814a975 100644
--- a/example/example_backend_nacm.c
+++ b/example/example_backend_nacm.c
@@ -44,6 +44,7 @@
#include
#include
#include
+#include
/* clicon */
#include
@@ -119,7 +120,7 @@ clixon_plugin_init(clicon_handle h)
clicon_debug(1, "%s backend nacm", __FUNCTION__);
nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE");
if (nacm_mode==NULL || strcmp(nacm_mode, "disabled") == 0){
- clicon_debug(1, "%s CLICON_NACM_MODE not enabled: example nacm module disabled", __FUNCTION__);
+ clicon_log(LOG_WARNING, "%s CLICON_NACM_MODE not enabled: example nacm module disabled", __FUNCTION__);
return NULL;
}
return &api;
diff --git a/example/example_cli.c b/example/example_cli.c
index f5c11b29..99c38368 100644
--- a/example/example_cli.c
+++ b/example/example_cli.c
@@ -68,11 +68,7 @@ mycallback(clicon_handle h, cvec *cvv, cvec *argv)
/* Show eth0 interfaces config using XPATH */
if (clicon_rpc_get_config(h, "running",
-#ifdef COMPAT_XSL
- "/interfaces/interface[name=eth0]",
-#else
"/interfaces/interface[name='eth0']",
-#endif
&xret) < 0)
goto done;
diff --git a/example/example_cli.cli b/example/example_cli.cli
index 3703e8ba..1856c8c7 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("EXAMPLE", "1", "text");
+no("Negate") notify("Get notifications from backend"), cli_notify("EXAMPLE", "0", "xml");
lock,cli_lock("candidate");
unlock,cli_unlock("candidate");
\ No newline at end of file
diff --git a/example/example_restconf.c b/example/example_restconf.c
index 2b22ce16..6cc6323d 100644
--- a/example/example_restconf.c
+++ b/example/example_restconf.c
@@ -39,7 +39,7 @@
#include
#include
#include
-#include
+#include
#include
/* cligen */
diff --git a/include/clixon_config.h.in b/include/clixon_config.h.in
index 5b0cb825..f33c59ad 100644
--- a/include/clixon_config.h.in
+++ b/include/clixon_config.h.in
@@ -6,6 +6,9 @@
/* Location for apps to find default config file */
#undef CLIXON_DEFAULT_CONFIG
+/* Enable publish of notification streams using SSE and curl */
+#undef CLIXON_PUBLISH_STREAMS
+
/* Clixon major release */
#undef CLIXON_VERSION_MAJOR
diff --git a/include/clixon_custom.h b/include/clixon_custom.h
index 9a9fa38c..8bad0f92 100644
--- a/include/clixon_custom.h
+++ b/include/clixon_custom.h
@@ -43,11 +43,6 @@
int strverscmp (__const char *__s1, __const char *__s2);
#endif
-/* Set if you want to enable "v" cli callback functions, such as cli_setv()
- * This was obsoleted in 3.7
- */
-#undef COMPAT_CLIV
-
/* Set if you want to assert that all rpc messages have set username
*/
#undef RPC_USERNAME_ASSERT
@@ -56,13 +51,3 @@ int strverscmp (__const char *__s1, __const char *__s2);
*/
#define XMLNS_YANG_ONLY 1
-/* Set if you want to enable old xpath functions in clixon_xsl.* instead of the
- * the new xpath functions in clixon_xpath.*
- * Note that when changing from old xpath code to new, calls on the form
- * `x[a=str]` where `str` is a string (not a number or XML symbol),
- * must be changed to: `x[a='str'] or x[a="str"]`
- * Enabling COMPAT_XSL may make sense if you have written a lot of user code that
- * relieson the error above. Or if a bug appears in the newimplementation.
- * @see test/lib.sh
- */
-#undef COMPAT_XSL
diff --git a/lib/clixon/clixon.h.in b/lib/clixon/clixon.h.in
index dfa82bc4..c454835f 100644
--- a/lib/clixon/clixon.h.in
+++ b/lib/clixon/clixon.h.in
@@ -63,11 +63,11 @@
#define LIBCLIXON_API 1
#include
-#include
#include
#include
#include
#include
+#include
#include
#include
#include
@@ -75,13 +75,14 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
#include
#include
#include
-#include
#include
#include
#include
diff --git a/lib/clixon/clixon_err.h b/lib/clixon/clixon_err.h
index 8b8900ac..b5961453 100644
--- a/lib/clixon/clixon_err.h
+++ b/lib/clixon/clixon_err.h
@@ -55,7 +55,7 @@ enum clicon_err{
OE_DB = 1, /* database registries */
OE_DEMON, /* demons: pidfiles, etc */
OE_EVENTS, /* events, filedescriptors, timeouts */
- OE_CFG, /* config commit / quagga */
+ OE_CFG, /* configuration */
OE_PROTO, /* config/client communication */
OE_REGEX, /* Regexp error */
OE_UNIX, /* unix/linux syscall error */
diff --git a/lib/clixon/clixon_event.h b/lib/clixon/clixon_event.h
index 626a4b27..99b598e2 100644
--- a/lib/clixon/clixon_event.h
+++ b/lib/clixon/clixon_event.h
@@ -43,6 +43,8 @@
*/
int clicon_exit_set(void);
+int clicon_exit_reset(void);
+
int clicon_exit_get(void);
int event_reg_fd(int fd, int (*fn)(int, void*), void *arg, char *str);
diff --git a/lib/clixon/clixon_handle.h b/lib/clixon/clixon_handle.h
index 897560c3..711e6cb8 100644
--- a/lib/clixon/clixon_handle.h
+++ b/lib/clixon/clixon_handle.h
@@ -74,4 +74,10 @@ clicon_hash_t *clicon_options(clicon_handle h);
/* Return internal clicon data (hash-array) given a handle.*/
clicon_hash_t *clicon_data(clicon_handle h);
+/* Return internal stream hash-array given a handle.*/
+struct event_stream *clicon_stream(clicon_handle h);
+struct event_stream;
+int clicon_stream_set(clicon_handle h, struct event_stream *es);
+int clicon_stream_append(clicon_handle h, struct event_stream *es);
+
#endif /* _CLIXON_HANDLE_H_ */
diff --git a/lib/clixon/clixon_log.h b/lib/clixon/clixon_log.h
index 7b833427..7db79142 100644
--- a/lib/clixon/clixon_log.h
+++ b/lib/clixon/clixon_log.h
@@ -44,24 +44,21 @@
#define CLICON_LOG_SYSLOG 1 /* print logs on syslog */
#define CLICON_LOG_STDERR 2 /* print logs on stderr */
#define CLICON_LOG_STDOUT 4 /* print logs on stdout */
-
-/*
- * Types
- */
-typedef int (clicon_log_notify_t)(int level, char *msg, void *arg);
+#define CLICON_LOG_FILE 8 /* print logs on clicon_log_filename */
/*
* Variables
*/
extern int debug;
-
/*
* Prototypes
*/
int clicon_log_init(char *ident, int upto, int flags);
+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)));
@@ -69,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_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h
index f65bc9dc..cdf570e9 100644
--- a/lib/clixon/clixon_netconf_lib.h
+++ b/lib/clixon/clixon_netconf_lib.h
@@ -62,5 +62,7 @@ int netconf_operation_failed(cbuf *cb, char *type, char *message);
int netconf_operation_failed_xml(cxobj **xret, char *type, char *message);
int netconf_malformed_message(cbuf *cb, char *message);
int netconf_malformed_message_xml(cxobj **xret, char *message);
+int netconf_trymerge(cxobj *x, yang_spec *yspec, cxobj **xret);
+int netconf_module_load(clicon_handle h);
#endif /* _CLIXON_NETCONF_LIB_H */
diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h
index 1f95c73a..f06660f0 100644
--- a/lib/clixon/clixon_options.h
+++ b/lib/clixon/clixon_options.h
@@ -81,7 +81,8 @@ enum startup_mode_t{
/* Print registry on file. For debugging. */
void clicon_option_dump(clicon_handle h, int dblevel);
/* Initialize options: set defaults, read config-file, etc */
-int clicon_options_main(clicon_handle h);
+int clicon_options_main(clicon_handle h, yang_spec *yspec);
+
/*! Check if a clicon option has a value */
int clicon_option_exists(clicon_handle h, const char *name);
@@ -164,11 +165,13 @@ int clicon_quiet_mode_set(clicon_handle h, int val);
yang_spec * clicon_dbspec_yang(clicon_handle h);
int clicon_dbspec_yang_set(clicon_handle h, struct yang_spec *ys);
-char *clicon_dbspec_name(clicon_handle h);
-int clicon_dbspec_name_set(clicon_handle h, char *name);
+#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
-yang_spec *clicon_netconf_yang(clicon_handle h);
-int clicon_netconf_yang_set(clicon_handle h, struct yang_spec *ys);
+cxobj *clicon_conf_xml(clicon_handle h);
+int clicon_conf_xml_set(clicon_handle h, cxobj *x);
plghndl_t clicon_xmldb_plugin_get(clicon_handle h);
int clicon_xmldb_plugin_set(clicon_handle h, plghndl_t handle);
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_queue.h b/lib/clixon/clixon_queue.h
index 2997c655..70127756 100644
--- a/lib/clixon/clixon_queue.h
+++ b/lib/clixon/clixon_queue.h
@@ -37,17 +37,30 @@
#ifndef _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 {
struct _qelem_t *q_next;
struct _qelem_t *q_prev;
} 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) { \
register qelem_t *Xe = (qelem_t *) (elem); \
register qelem_t *Xp = (qelem_t *) (pred); \
@@ -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) { \
register qelem_t *Xe = (qelem_t *) (elem); \
@@ -79,9 +98,16 @@ typedef struct _qelem_t {
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'.
+ * @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) { \
register qelem_t *Xe = (qelem_t *) elem; \
@@ -92,10 +118,13 @@ typedef struct _qelem_t {
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))
-
#endif /* _CLIXON_QUEUE_H_ */
diff --git a/lib/clixon/clixon_stream.h b/lib/clixon/clixon_stream.h
new file mode 100644
index 00000000..a3a1457f
--- /dev/null
+++ b/lib/clixon/clixon_stream.h
@@ -0,0 +1,116 @@
+/*
+ *
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLIXON.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ Alternatively, the contents of this file may be used under the terms of
+ the GNU General Public License Version 3 or later (the "GPL"),
+ in which case the provisions of the GPL are applicable instead
+ of those above. If you wish to allow use of your version of this file only
+ under the terms of the GPL, and not to allow others to
+ use your version of this file under the terms of Apache License version 2,
+ indicate your decision by deleting the provisions above and replace them with
+ the notice and other provisions required by the GPL. If you do not delete
+ the provisions above, a recipient may use your version of this file under
+ the terms of any one of the Apache License version 2 or the GPL.
+
+ ***** END LICENSE BLOCK *****
+
+ * Event notification streams according to RFC5277
+ */
+#ifndef _CLIXON_STREAM_H_
+#define _CLIXON_STREAM_H_
+
+/*
+ * Types
+ */
+/* Subscription callback
+ * @param[in] h Clicon handle
+ * @param[in] op Operation: 0 OK, 1 Close
+ * @param[in] event Event as XML
+ * @param[in] arg Extra argument provided in stream_ss_add
+ * @see stream_ss_add
+ */
+typedef int (*stream_fn_t)(clicon_handle h, int op, cxobj *event, void *arg);
+
+struct stream_subscription{
+ qelem_t ss_q; /* queue header */
+ char *ss_stream; /* Name of associated stream */
+ char *ss_xpath; /* Filter selector as xpath */
+ struct timeval ss_starttime; /* Replay starttime */
+ struct timeval ss_stoptime; /* Replay stoptime */
+ stream_fn_t ss_fn; /* Callback when event occurs */
+ 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
+ */
+struct event_stream{
+ qelem_t es_q; /* queue header */
+ char *es_name; /* name of notification event stream */
+ char *es_description;
+ struct stream_subscription *es_subscription;
+ int es_replay_enabled; /* set if replay is enables */
+ struct timeval es_retention; /* replay retention - how much to save */
+ struct stream_replay *es_replay;
+
+};
+typedef struct event_stream event_stream_t;
+
+/*
+ * Prototypes
+ */
+event_stream_t *stream_find(clicon_handle h, const char *name);
+int stream_add(clicon_handle h, const char *name, const char *description, int replay_enabled, struct timeval *retention);
+int stream_delete_all(clicon_handle h);
+int stream_get_xml(clicon_handle h, int access, cbuf *cb);
+int stream_timer_setup(int fd, void *arg);
+/* Subscriptions */
+struct stream_subscription *stream_ss_add(clicon_handle h, char *stream,
+ char *xpath, struct timeval *start, struct timeval *stop,
+ stream_fn_t fn, void *arg);
+int stream_ss_rm(clicon_handle h, event_stream_t *es, struct stream_subscription *ss);
+struct stream_subscription *stream_ss_find(event_stream_t *es,
+ stream_fn_t fn, void *arg);
+int stream_ss_delete_all(clicon_handle h, stream_fn_t fn, void *arg);
+int stream_ss_delete(clicon_handle h, char *name, stream_fn_t fn, void *arg);
+
+int stream_notify_xml(clicon_handle h, char *stream, cxobj *xml);
+#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
+
+/* Replay */
+int stream_replay_add(event_stream_t *es, struct timeval *tv, cxobj *xv);
+int stream_replay_trigger(clicon_handle h, char *stream, stream_fn_t fn, void *arg);
+
+/* Experimental publish streams using SSE. CLIXON_PUBLISH_STREAMS should be set */
+int stream_publish(clicon_handle h, char *stream);
+int stream_publish_init();
+int stream_publish_exit();
+
+#endif /* _CLIXON_STREAM_H_ */
diff --git a/lib/clixon/clixon_string.h b/lib/clixon/clixon_string.h
index 5b5789dd..cebb0871 100644
--- a/lib/clixon/clixon_string.h
+++ b/lib/clixon/clixon_string.h
@@ -76,9 +76,14 @@ static inline char * strdup4(char *str)
char **clicon_strsep(char *string, char *delim, int *nvec0);
char *clicon_strjoin (int argc, char **argv, char *delim);
int str2cvec(char *string, char delim1, char delim2, cvec **cvp);
-int uri_percent_encode(char *str, char **escp);
-int uri_percent_decode(char *esc, char **str);
-int xml_chardata_encode(char *str, char **escp);
+#if defined(__GNUC__) && __GNUC__ >= 3
+int uri_percent_encode(char **encp, char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
+int xml_chardata_encode(char **escp, char *fmt, ... ) __attribute__ ((format (printf, 2, 3)));
+#else
+int uri_percent_encode(char **encp, char *str, ...);
+int xml_chardata_encode(char **escp, char *fmt, ...);
+#endif
+int uri_percent_decode(char *enc, char **str);
const char *clicon_int2str(const map_str2int *mstab, int i);
int clicon_str2int(const map_str2int *mstab, char *str);
diff --git a/lib/clixon/clixon_xml_sort.h b/lib/clixon/clixon_xml_sort.h
index 0a7ee662..620456a0 100644
--- a/lib/clixon/clixon_xml_sort.h
+++ b/lib/clixon/clixon_xml_sort.h
@@ -44,9 +44,9 @@ extern int xml_child_sort;
/*
* Prototypes
*/
-int xml_child_spec(char *name, cxobj *xp, yang_spec *yspec, yang_stmt **yp);
-int xml_cmp(const void* arg1, const void* arg2);
-int xml_sort(cxobj *x0, void *arg);
+int xml_child_spec(char *name, cxobj *xp, yang_spec *yspec, yang_stmt **yp);
+int xml_cmp(const void* arg1, const void* arg2);
+int xml_sort(cxobj *x0, void *arg);
cxobj *xml_search(cxobj *x, char *name, int yangi, enum rfc_6020 keyword, int keynr, char **keyvec, char **keyval);
int xml_insert_pos(cxobj *x0, char *name, int yangi, enum rfc_6020 keyword,
int keynr, char **keyvec, char **keyval, int low,
diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h
index 18fbee6b..3399d4fa 100644
--- a/lib/clixon/clixon_yang.h
+++ b/lib/clixon/clixon_yang.h
@@ -57,7 +57,8 @@
* - Cant use the symbols in this file because yacc needs token definitions
*/
enum rfc_6020{
- Y_ANYXML = 0,
+ Y_ACTION = 0,
+ Y_ANYXML,
Y_ARGUMENT,
Y_AUGMENT,
Y_BASE,
@@ -258,9 +259,11 @@ char *yang_find_myprefix(yang_stmt *ys);
int yang_order(yang_stmt *y);
int yang_print(FILE *f, yang_node *yn);
int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal);
+int ys_populate(yang_stmt *ys, void *arg);
yang_stmt *yang_parse_file(int fd, const char *name, yang_spec *ysp);
-int yang_parse(clicon_handle h, const char *yang_dir,
- const char *module, const char *revision, yang_spec *ysp);
+int yang_parse(clicon_handle h, const char *filename,
+ const char *module, const char *dir,
+ const char *revision, yang_spec *ysp, yang_stmt **ymodp);
int yang_apply(yang_node *yn, enum rfc_6020 key, yang_applyfn_t fn,
void *arg);
int yang_abs_schema_nodeid(yang_spec *yspec, char *schema_nodeid,
@@ -271,8 +274,8 @@ cg_var *ys_parse(yang_stmt *ys, enum cv_type cvtype);
int ys_parse_sub(yang_stmt *ys, char *extra);
int yang_mandatory(yang_stmt *ys);
int yang_config(yang_stmt *ys);
-yang_spec *yang_spec_netconf(clicon_handle h);
-yang_spec *yang_spec_main(clicon_handle h);
+int yang_spec_parse_module(clicon_handle h, char *module, char *dir, char *revision, yang_spec *yspec, yang_stmt **ymodp);
+int yang_spec_parse_file(clicon_handle h, char *filename, char *dir, yang_spec *yspec, yang_stmt **ymodp);
cvec *yang_arg2cvec(yang_stmt *ys, char *delimi);
int yang_key_match(yang_node *yn, char *name);
diff --git a/lib/clixon/clixon_yang_module.h b/lib/clixon/clixon_yang_module.h
new file mode 100644
index 00000000..0ca4760c
--- /dev/null
+++ b/lib/clixon/clixon_yang_module.h
@@ -0,0 +1,57 @@
+/*
+ *
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLIXON.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ Alternatively, the contents of this file may be used under the terms of
+ the GNU General Public License Version 3 or later (the "GPL"),
+ in which case the provisions of the GPL are applicable instead
+ of those above. If you wish to allow use of your version of this file only
+ under the terms of the GPL, and not to allow others to
+ use your version of this file under the terms of Apache License version 2,
+ indicate your decision by deleting the provisions above and replace them with
+ the notice and other provisions required by the GPL. If you do not delete
+ the provisions above, a recipient may use your version of this file under
+ the terms of any one of the Apache License version 2 or the GPL.
+
+ ***** END LICENSE BLOCK *****
+
+ * Yang module and feature handling
+ * @see https://tools.ietf.org/html/rfc7895
+ */
+
+#ifndef _CLIXON_YANG_MODULE_H_
+#define _CLIXON_YANG_MODULE_H_
+
+/*
+ * Constants
+ */
+
+/*
+ * Types
+ */
+
+
+/*
+ * Prototypes
+ */
+int yang_modules_init(clicon_handle h);
+char *yang_modules_revision(clicon_handle h);
+int yang_modules_state_get(clicon_handle h, yang_spec *yspec, cxobj **xret);
+
+#endif /* _CLIXON_YANG_MODULE_H_ */
diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in
index a37680c9..787c19c8 100644
--- a/lib/src/Makefile.in
+++ b/lib/src/Makefile.in
@@ -69,11 +69,11 @@ INCLUDES = -I. @INCLUDES@ -I$(top_srcdir)/lib/clixon -I$(top_srcdir)/include -I$
SRC = clixon_sig.c clixon_log.c clixon_err.c clixon_event.c \
clixon_string.c clixon_handle.c \
clixon_xml.c clixon_xml_sort.c clixon_xml_map.c clixon_file.c \
- clixon_json.c clixon_yang.c clixon_yang_type.c \
+ clixon_json.c clixon_yang.c clixon_yang_type.c clixon_yang_module.c \
clixon_hash.c clixon_options.c clixon_plugin.c \
clixon_proto.c clixon_proto_client.c \
- clixon_xsl.c clixon_xpath.c clixon_xpath_ctx.c clixon_sha1.c \
- clixon_xml_db.c clixon_netconf_lib.c
+ clixon_xpath.c clixon_xpath_ctx.c clixon_sha1.c \
+ clixon_xml_db.c clixon_netconf_lib.c clixon_stream.c
YACCOBJS := lex.clixon_xml_parse.o clixon_xml_parse.tab.o \
lex.clixon_yang_parse.o clixon_yang_parse.tab.o \
diff --git a/lib/src/clixon_event.c b/lib/src/clixon_event.c
index 1569f69f..d15e12c5 100644
--- a/lib/src/clixon_event.c
+++ b/lib/src/clixon_event.c
@@ -75,6 +75,7 @@ struct event_data{
/*
* Internal variables
+ * XXX consider use handle variables instead of global
*/
static struct event_data *ee = NULL;
static struct event_data *ee_timers = NULL;
@@ -86,7 +87,7 @@ static int _clicon_exit = 0;
/*! For signal handlers: instead of doing exit, set a global variable to exit
* Status is then checked in event_loop.
- * Note it maybe would be better to do use on a handle basis, bit a signal
+ * Note it maybe would be better to do use on a handle basis, but a signal
* handler is global
*/
int
@@ -96,6 +97,15 @@ clicon_exit_set(void)
return 0;
}
+/*! Set exit to 0
+ */
+int
+clicon_exit_reset(void)
+{
+ _clicon_exit = 0;
+ return 0;
+}
+
/*! Get the status of global exit variable, usually set by signal handlers
*/
int
@@ -274,6 +284,8 @@ event_poll(int fd)
/*! Dispatch file descriptor events (and timeouts) by invoking callbacks.
* There is an issue with fairness that timeouts may take over all events
* One could try to poll the file descriptors after a timeout?
+ * @retval 0 OK
+ * @retval -1 Error: eg select, callback, timer,
*/
int
event_loop(void)
diff --git a/lib/src/clixon_file.c b/lib/src/clixon_file.c
index 160d3145..22998553 100644
--- a/lib/src/clixon_file.c
+++ b/lib/src/clixon_file.c
@@ -112,8 +112,7 @@ clicon_file_dirent(const char *dir,
char errbuf[128];
char filename[MAXPATHLEN];
struct stat st;
- struct dirent dent;
- struct dirent *dresp;
+ struct dirent *dent;
struct dirent *tmp;
struct dirent *new = NULL;
struct dirent *dvecp = NULL;
@@ -132,22 +131,15 @@ clicon_file_dirent(const char *dir,
clicon_err(OE_UNIX, errno, "opendir(%s)", dir);
goto quit;
}
- for (res = readdir_r(dirp, &dent, &dresp);
- dresp;
- res = readdir_r(dirp, &dent, &dresp)) {
- if (res != 0) {
- clicon_err(OE_UNIX, 0, "readdir: %s", strerror(errno));
- goto quit;
- }
-
+ while((dent = readdir(dirp)) != NULL) {
/* Filename matching */
if (regexp) {
- if (regexec(&re, dent.d_name, (size_t) 0, NULL, 0) != 0)
+ if (regexec(&re, dent->d_name, (size_t) 0, NULL, 0) != 0)
continue;
}
/* File type matching */
if (type) {
- snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dent.d_name);
+ snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dent->d_name);
res = lstat(filename, &st);
if (res != 0) {
clicon_err(OE_UNIX, 0, "lstat: %s", strerror(errno));
@@ -161,7 +153,7 @@ clicon_file_dirent(const char *dir,
goto quit;
}
new = tmp;
- memcpy(&new[nent], &dent, sizeof(dent));
+ memcpy(&new[nent], dent, sizeof(*dent));
nent++;
} /* while */
diff --git a/lib/src/clixon_handle.c b/lib/src/clixon_handle.c
index edac1382..fd30f0b7 100644
--- a/lib/src/clixon_handle.c
+++ b/lib/src/clixon_handle.c
@@ -53,6 +53,8 @@
#include "clixon_log.h"
#include "clixon_err.h"
#include "clixon_yang.h"
+#include "clixon_xml.h"
+#include "clixon_stream.h"
#include "clixon_options.h"
#define CLICON_MAGIC 0x99aafabe
@@ -65,9 +67,10 @@
* @see struct backend_handle
*/
struct clicon_handle {
- int ch_magic; /* magic (HDR) */
- clicon_hash_t *ch_copt; /* clicon option list (HDR) */
- clicon_hash_t *ch_data; /* internal clicon data (HDR) */
+ int ch_magic; /* magic (HDR) */
+ clicon_hash_t *ch_copt; /* clicon option list (HDR) */
+ clicon_hash_t *ch_data; /* internal clicon data (HDR) Unclear why two? */
+ event_stream_t *ch_stream; /* notification streams, see clixon_stream.[ch] */
};
/*! Internal call to allocate a CLICON handle.
@@ -126,13 +129,13 @@ int
clicon_handle_exit(clicon_handle h)
{
struct clicon_handle *ch = handle(h);
- clicon_hash_t *copt;
- clicon_hash_t *data;
+ clicon_hash_t *ha;
- if ((copt = clicon_options(h)) != NULL)
- hash_free(copt);
- if ((data = clicon_data(h)) != NULL)
- hash_free(data);
+ if ((ha = clicon_options(h)) != NULL)
+ hash_free(ha);
+ if ((ha = clicon_data(h)) != NULL)
+ hash_free(ha);
+ stream_delete_all(h);
free(ch);
return 0;
}
@@ -172,3 +175,34 @@ clicon_data(clicon_handle h)
return ch->ch_data;
}
+
+/*! Return stream hash-array given a clicon handle.
+ * @param[in] h Clicon handle
+ */
+event_stream_t *
+clicon_stream(clicon_handle h)
+{
+ struct clicon_handle *ch = handle(h);
+
+ return ch->ch_stream;
+}
+
+int
+clicon_stream_set(clicon_handle h,
+ event_stream_t *es)
+{
+ struct clicon_handle *ch = handle(h);
+
+ ch->ch_stream = es;
+ return 0;
+}
+
+int
+clicon_stream_append(clicon_handle h,
+ event_stream_t *es)
+{
+ struct clicon_handle *ch = handle(h);
+
+ ADDQ(es, ch->ch_stream);
+ return 0;
+}
diff --git a/lib/src/clixon_log.c b/lib/src/clixon_log.c
index 21d24bcc..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,12 +65,8 @@ 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 bypass logging and write debug messages directly to file */
-static FILE *_debugfile = NULL;
+/* Set to open file to write debug messages directly to file */
+static FILE *_logfile = NULL;
/*! Initialize system logger.
*
@@ -81,19 +80,75 @@ static FILE *_debugfile = 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)
{
- if (setlogmask(LOG_UPTO(upto)) < 0)
- /* Cant syslog here */
- fprintf(stderr, "%s: setlogmask: %s\n", __FUNCTION__, strerror(errno));
_logflags = flags;
- openlog(ident, LOG_PID, LOG_USER); /* LOG_PUSER is achieved by direct stderr logs in clicon_log */
+ if (flags & CLICON_LOG_SYSLOG){
+ if (setlogmask(LOG_UPTO(upto)) < 0)
+ /* Cant syslog here */
+ fprintf(stderr, "%s: setlogmask: %s\n", __FUNCTION__, strerror(errno));
+ openlog(ident, LOG_PID, LOG_USER); /* LOG_PUSER is achieved by direct stderr logs in clicon_log */
+ }
+ return 0;
+}
+
+int
+clicon_log_exit(void)
+{
+ if (_logfile)
+ fclose(_logfile);
+ return 0;
+}
+
+/*! Utility function to set log destination/flag using command-line option
+ * @param[in] c Log option,one of s,f,e,o
+ * @retval -1 No match
+ * @retval 0 One of CLICON_LOG_SYSLOG|STDERR|STDOUT|FILE
+ */
+int
+clicon_log_opt(char c)
+{
+ int logdst = -1;
+
+ switch (c){
+ case 's':
+ logdst = CLICON_LOG_SYSLOG;
+ break;
+ case 'e':
+ logdst = CLICON_LOG_STDERR;
+ break;
+ case 'o':
+ logdst = CLICON_LOG_STDOUT;
+ break;
+ case 'f':
+ logdst = CLICON_LOG_FILE;
+ break;
+ default:
+ break;
+ }
+ return logdst;
+}
+
+/*! If log flags include CLICON_LOG_FILE, set the file
+ * @param[in] filename File to log to
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+int
+clicon_log_file(char *filename)
+{
+ if (_logfile)
+ fclose(_logfile);
+ if ((_logfile = fopen(filename, "a")) == NULL){
+ fprintf(stderr, "fopen: %s\n", strerror(errno)); /* dont use clicon_err here due to recursion */
+ return -1;
+ }
return 0;
}
@@ -103,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
@@ -131,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.
@@ -154,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);
@@ -183,30 +227,15 @@ clicon_log_str(int level,
flogtime(stdout);
fprintf(stdout, "%s\n", msg);
}
- 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--;
+ if ((_logflags & CLICON_LOG_FILE) && _logfile){
+ flogtime(_logfile);
+ fprintf(_logfile, "%s\n", msg);
+ fflush(_logfile);
}
+
+ /* Enable this if you want syslog in a stream. But there are problems with
+ * recursion
+ */
done:
return 0;
}
@@ -326,13 +355,7 @@ clicon_debug(int dbglevel,
goto done;
}
va_end(args);
- if (_debugfile != NULL){ /* Bypass syslog altogether */
- /* XXX: Here use date sub-routine as found in err_print1 */
- flogtime(_debugfile);
- fprintf(_debugfile, "%s\n", msg);
- }
- else
- clicon_log_str(LOG_DEBUG, msg);
+ clicon_log_str(LOG_DEBUG, msg);
retval = 0;
done:
if (msg)
diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c
index a7cf2f9e..d832ffc9 100644
--- a/lib/src/clixon_netconf_lib.c
+++ b/lib/src/clixon_netconf_lib.c
@@ -60,6 +60,8 @@
#include "clixon_yang.h"
#include "clixon_log.h"
#include "clixon_xml.h"
+#include "clixon_options.h"
+#include "clixon_xml_map.h"
#include "clixon_netconf_lib.h"
/*! Create Netconf in-use error XML tree according to RFC 6241 Appendix A
@@ -84,7 +86,7 @@ netconf_in_use(cbuf *cb,
type) <0)
goto err;
if (message){
- if (xml_chardata_encode(message, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "%s", encstr) < 0)
goto err;
@@ -123,7 +125,7 @@ netconf_invalid_value(cbuf *cb,
type) <0)
goto err;
if (message){
- if (xml_chardata_encode(message, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "%s", encstr) < 0)
goto err;
@@ -163,7 +165,7 @@ netconf_too_big(cbuf *cb,
type) <0)
goto err;
if (message){
- if (xml_chardata_encode(message, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "%s", encstr) < 0)
goto err;
@@ -205,7 +207,7 @@ netconf_missing_attribute(cbuf *cb,
type, info) <0)
goto err;
if (message){
- if (xml_chardata_encode(message, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "%s", encstr) < 0)
goto err;
@@ -246,7 +248,7 @@ netconf_bad_attribute(cbuf *cb,
type, info) <0)
goto err;
if (message){
- if (xml_chardata_encode(message, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "%s", encstr) < 0)
goto err;
@@ -288,7 +290,7 @@ netconf_unknown_attribute(cbuf *cb,
type, info) <0)
goto err;
if (message){
- if (xml_chardata_encode(message, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "%s", encstr) < 0)
goto err;
@@ -330,7 +332,7 @@ netconf_missing_element(cbuf *cb,
type, info) <0)
goto err;
if (message){
- if (xml_chardata_encode(message, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "%s", encstr) < 0)
goto err;
@@ -373,7 +375,7 @@ netconf_bad_element(cbuf *cb,
type, info) <0)
goto err;
if (message){
- if (xml_chardata_encode(message, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "%s", encstr) < 0)
goto err;
@@ -415,7 +417,7 @@ netconf_unknown_element(cbuf *cb,
type, info) <0)
goto err;
if (message){
- if (xml_chardata_encode(message, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "%s", encstr) < 0)
goto err;
@@ -457,7 +459,7 @@ netconf_unknown_namespace(cbuf *cb,
type, info) <0)
goto err;
if (message){
- if (xml_chardata_encode(message, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "%s", encstr) < 0)
goto err;
@@ -496,7 +498,7 @@ netconf_access_denied(cbuf *cb,
type) <0)
goto err;
if (message){
- if (xml_chardata_encode(message, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "%s", encstr) < 0)
goto err;
@@ -572,7 +574,7 @@ netconf_lock_denied(cbuf *cb,
info) <0)
goto err;
if (message){
- if (xml_chardata_encode(message, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "%s", encstr) < 0)
goto err;
@@ -611,7 +613,7 @@ netconf_resource_denied(cbuf *cb,
type) <0)
goto err;
if (message){
- if (xml_chardata_encode(message, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "%s", encstr) < 0)
goto err;
@@ -651,7 +653,7 @@ netconf_rollback_failed(cbuf *cb,
type) <0)
goto err;
if (message){
- if (xml_chardata_encode(message, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "%s", encstr) < 0)
goto err;
@@ -689,7 +691,7 @@ netconf_data_exists(cbuf *cb,
"error") <0)
goto err;
if (message){
- if (xml_chardata_encode(message, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "%s", encstr) < 0)
goto err;
@@ -727,7 +729,7 @@ netconf_data_missing(cbuf *cb,
"error") <0)
goto err;
if (message){
- if (xml_chardata_encode(message, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "%s", encstr) < 0)
goto err;
@@ -767,7 +769,7 @@ netconf_operation_not_supported(cbuf *cb,
type) <0)
goto err;
if (message){
- if (xml_chardata_encode(message, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "%s", encstr) < 0)
goto err;
@@ -807,7 +809,7 @@ netconf_operation_failed(cbuf *cb,
type) <0)
goto err;
if (message){
- if (xml_chardata_encode(message, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "%s", encstr) < 0)
goto err;
@@ -882,7 +884,7 @@ netconf_malformed_message(cbuf *cb,
"error") <0)
goto err;
if (message){
- if (xml_chardata_encode(message, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "%s", encstr) < 0)
goto err;
@@ -934,3 +936,76 @@ netconf_malformed_message_xml(cxobj **xret,
done:
return retval;
}
+
+/*! Help function: merge - check yang - if error make netconf errmsg
+ * @param[in] x XML tree
+ * @param[in] yspec Yang spec
+ * @param[in,out] xret Existing XML tree, merge x into this
+ * @retval -1 Error (fatal)
+ * @retval 0 OK
+ * @retval 1 Statedata callback failed
+ */
+int
+netconf_trymerge(cxobj *x,
+ yang_spec *yspec,
+ cxobj **xret)
+{
+ int retval = -1;
+ char *reason = NULL;
+ cxobj *xc;
+
+ if (xml_merge(*xret, x, yspec, &reason) < 0)
+ goto done;
+ if (reason){
+ while ((xc = xml_child_i(*xret, 0)) != NULL)
+ xml_purge(xc);
+ if (netconf_operation_failed_xml(xret, "rpc", reason)< 0)
+ goto done;
+ retval = 1;
+ goto done;
+ }
+ retval = 0;
+ done:
+ if (reason)
+ free(reason);
+ return retval;
+}
+
+/*! Load ietf netconf yang module and set enabled features
+ * The features added are:
+ * candidate (8.3)
+ * validate (8.6)
+ * startup (8.7)
+ * xpath (8.9)
+ */
+int
+netconf_module_load(clicon_handle h)
+{
+ int retval = -1;
+ cxobj *xc;
+ // cxobj *x;
+ yang_spec *yspec;
+
+ yspec = clicon_dbspec_yang(h);
+ /* Load yang spec */
+ if (yang_spec_parse_module(h, "ietf-netconf", CLIXON_DATADIR, NULL, yspec, NULL)< 0)
+ goto done;
+ if (yang_spec_parse_module(h, "ietf-netconf-notification", CLIXON_DATADIR, NULL, yspec, NULL)< 0)
+ goto done;
+ if ((xc = clicon_conf_xml(h)) == NULL){
+ clicon_err(OE_CFG, ENOENT, "Clicon configuration not loaded");
+ goto done;
+ }
+ /* Enable features (hardcoded here) */
+ if (xml_parse_string("ietf-netconf:candidate", yspec, &xc) < 0)
+ goto done;
+ if (xml_parse_string("ietf-netconf:validate", yspec, &xc) < 0)
+ goto done;
+ if (xml_parse_string("ietf-netconf:startup", yspec, &xc) < 0)
+ goto done;
+ if (xml_parse_string("ietf-netconf:xpath", yspec, &xc) < 0)
+ goto done;
+ retval = 0;
+ done:
+ return retval;
+}
diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c
index cfd0d76c..62202def 100644
--- a/lib/src/clixon_options.c
+++ b/lib/src/clixon_options.c
@@ -64,10 +64,9 @@
#include "clixon_handle.h"
#include "clixon_log.h"
#include "clixon_yang.h"
-#include "clixon_options.h"
#include "clixon_xml.h"
+#include "clixon_options.h"
#include "clixon_plugin.h"
-#include "clixon_xsl.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
#include "clixon_xml_map.h"
@@ -117,12 +116,16 @@ clicon_option_dump(clicon_handle h,
}
/*! Read filename and set values to global options registry. XML variant.
- * @see clicon_option_readfile
+ *
+ * @param[out] xconfig Pointer to xml config tree. Should be freed by caller
+ * @retval 0 OK
+ * @retval -1 Error
*/
-static int
-clicon_option_readfile_xml(clicon_hash_t *copt,
- const char *filename,
- yang_spec *yspec)
+static int
+parse_configfile(clicon_handle h,
+ const char *filename,
+ yang_spec *yspec,
+ cxobj **xconfig)
{
struct stat st;
FILE *f = NULL;
@@ -133,6 +136,7 @@ clicon_option_readfile_xml(clicon_hash_t *copt,
cxobj *x = NULL;
char *name;
char *body;
+ clicon_hash_t *copt = clicon_options(h);
if (filename == NULL || !strlen(filename)){
clicon_err(OE_UNIX, 0, "Not specified");
@@ -169,14 +173,22 @@ clicon_option_readfile_xml(clicon_hash_t *copt,
while ((x = xml_child_each(xc, x, CX_ELMNT)) != NULL) {
name = xml_name(x);
body = xml_body(x);
- if (name && body &&
- (hash_add(copt,
- name,
- body,
- strlen(body)+1)) == NULL)
+ if (name==NULL || body == NULL){
+ clicon_log(LOG_WARNING, "%s option NULL: name:%s body:%s",
+ __FUNCTION__, name, body);
+ continue;
+ }
+ if (strcmp(name,"CLICON_FEATURE")==0)
+ continue;
+ if (hash_add(copt,
+ name,
+ body,
+ strlen(body)+1) == NULL)
goto done;
}
retval = 0;
+ *xconfig = xt;
+ xt = NULL;
done:
if (xt)
xml_free(xt);
@@ -185,21 +197,27 @@ clicon_option_readfile_xml(clicon_hash_t *copt,
return retval;
}
-/*! Initialize option values
+/*! Parse clixon yang file. Parse XML config file. Initialize option values
*
* Set default options, Read config-file, Check that all values are set.
+ * Parse clixon yang file and save in yspec.
* Read clixon system config files
- * @param[in] h clicon handle
+ * @param[in] h clicon handle
+ * @param[in] yspec Yang spec of clixon config file
+ * @note Due to Bug: Top-level Yang symbol cannot be called "config" in any
+ * imported yang file, the config module needs to be isolated from all
+ * other yang modules.
*/
int
-clicon_options_main(clicon_handle h)
+clicon_options_main(clicon_handle h,
+ yang_spec *yspec)
{
int retval = -1;
char *configfile;
clicon_hash_t *copt = clicon_options(h);
char *suffix;
char xml = 0; /* Configfile is xml, otherwise legacy */
- yang_spec *yspec = NULL;
+ cxobj *xconfig = NULL;
/*
* Set configure file if not set by command-line above
@@ -214,36 +232,34 @@ clicon_options_main(clicon_handle h)
suffix++;
xml = strcmp(suffix, "xml") == 0;
}
- if (xml){ /* Read clixon yang file */
- if ((yspec = yspec_new()) == NULL)
- goto done;
- if (yang_parse(h, CLIXON_DATADIR, "clixon-config", NULL, yspec) < 0)
- goto done;
- /* Read configfile */
- if (clicon_option_readfile_xml(copt, configfile, yspec) < 0)
- goto done;
- /* Specific option handling */
- if (clicon_option_bool(h, "CLICON_XML_SORT") == 1)
- xml_child_sort = 1;
- else
- xml_child_sort = 0;
- }
- else {
+ if (xml == 0){
clicon_err(OE_CFG, 0, "%s: suffix %s not recognized (Run ./configure --with-config-compat?)", configfile, suffix);
goto done;
}
+ /* Parse clixon yang spec */
+ if (yang_parse(h, NULL, "clixon-config", CLIXON_DATADIR, NULL, yspec, NULL) < 0)
+ goto done;
+ /* Read configfile */
+ if (parse_configfile(h, configfile, yspec, &xconfig) < 0)
+ goto done;
+ if (xml_rootchild(xconfig, 0, &xconfig) < 0)
+ goto done;
+ clicon_conf_xml_set(h, xconfig);
+ /* Specific option handling */
+ if (clicon_option_bool(h, "CLICON_XML_SORT") == 1)
+ xml_child_sort = 1;
+ else
+ xml_child_sort = 0;
retval = 0;
done:
- if (yspec) /* The clixon yang-spec is not used after this */
- yspec_free(yspec);
-
return retval;
}
/*! Check if a clicon option has a value
- * @param[in] h clicon_handle
- * @param[in] name option name
- * @retval
+ * @param[in] h clicon_handle
+ * @param[in] name option name
+ * @retval !=0 option exists
+ * @retval 0 option does not exists
*/
int
clicon_option_exists(clicon_handle h,
@@ -572,26 +588,27 @@ clicon_dbspec_yang_set(clicon_handle h,
return 0;
}
-/*! Get YANG NETCONF specification
+#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_netconf_yang(clicon_handle h)
+clicon_config_yang(clicon_handle h)
{
clicon_hash_t *cdat = clicon_data(h);
size_t len;
void *p;
- if ((p = hash_value(cdat, "netconf_yang", &len)) != NULL)
+ if ((p = hash_value(cdat, "control_yang", &len)) != NULL)
return *(yang_spec **)p;
return NULL;
}
-/*! Set yang netconf specification
+/*! Set yang specification for control
* ys must be a malloced pointer
*/
int
-clicon_netconf_yang_set(clicon_handle h,
+clicon_config_yang_set(clicon_handle h,
struct yang_spec *ys)
{
clicon_hash_t *cdat = clicon_data(h);
@@ -599,29 +616,43 @@ clicon_netconf_yang_set(clicon_handle 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, "netconf_yang", &ys, sizeof(ys)) == NULL)
+ if (hash_add(cdat, "control_yang", &ys, sizeof(ys)) == NULL)
return -1;
return 0;
}
+#endif
-
-/*! Get dbspec name as read from spec. Can be used in CLI '@' syntax.
- * XXX: this we muśt change,...
+/*! Get YANG specification for Clixon system options and features
+ * Must use hash functions directly since they are not strings.
+ * Example: features are typically accessed directly in the config tree.
*/
-char *
-clicon_dbspec_name(clicon_handle h)
+cxobj *
+clicon_conf_xml(clicon_handle h)
{
- if (!clicon_option_exists(h, "dbspec_name"))
- return NULL;
- return clicon_option_str(h, "dbspec_name");
+ clicon_hash_t *cdat = clicon_data(h);
+ size_t len;
+ void *p;
+
+ if ((p = hash_value(cdat, "clixon_conf", &len)) != NULL)
+ return *(cxobj **)p;
+ return NULL;
}
-/*! Set dbspec name as read from spec. Can be used in CLI '@' syntax.
+/*! Set YANG specification for Clixon system options and features
+ * ys must be a malloced pointer
*/
int
-clicon_dbspec_name_set(clicon_handle h, char *name)
+clicon_conf_xml_set(clicon_handle h,
+ cxobj *x)
{
- return clicon_option_str_set(h, "dbspec_name", name);
+ clicon_hash_t *cdat = clicon_data(h);
+
+ /* It is the pointer to x that should be copied by hash,
+ * so we send a ptr to the ptr to indicate what to copy.
+ */
+ if (hash_add(cdat, "clixon_conf", &x, sizeof(x)) == NULL)
+ return -1;
+ return 0;
}
/*! Get xmldb datastore plugin handle, as used by dlopen/dlsym/dlclose */
@@ -717,7 +748,6 @@ clicon_xmldb_handle_set(clicon_handle h,
return 0;
}
-
/*! Get authorized user name
* @param[in] h Clicon handle
* @retval xh XMLDB storage handle. If not connected return NULL
diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c
index 2631938c..7710ae80 100644
--- a/lib/src/clixon_plugin.c
+++ b/lib/src/clixon_plugin.c
@@ -130,8 +130,10 @@ clixon_plugin_each_revert(clicon_handle h,
clixon_plugin *cp = NULL;
clixon_plugin *cpnext = NULL;
- if (cpprev == NULL)
- cpnext = &_clixon_plugins[nr-1];
+ if (cpprev == NULL){
+ if (nr>0)
+ cpnext = &_clixon_plugins[nr-1];
+ }
else{
for (i = nr-1; i >= 0; i--) {
cp = &_clixon_plugins[i];
@@ -189,7 +191,7 @@ plugin_load_one(clicon_handle h,
char *name;
char *p;
- clicon_debug(1, "%s", __FUNCTION__);
+ clicon_debug(1, "%s file:%s function:%s", __FUNCTION__, file, function);
dlerror(); /* Clear any existing error */
if ((handle = dlopen(file, dlflags)) == NULL) {
error = (char*)dlerror();
@@ -320,7 +322,7 @@ clixon_plugin_start(clicon_handle h,
continue;
// optind = 0;
if (startfn(h, argc, argv) < 0) {
- clicon_debug(1, "plugin_start() failed\n");
+ clicon_debug(1, "plugin_start() failed");
return -1;
}
}
@@ -343,7 +345,7 @@ clixon_plugin_exit(clicon_handle h)
if ((exitfn = cp->cp_api.ca_exit) == NULL)
continue;
if (exitfn(h) < 0) {
- clicon_debug(1, "plugin_exit() failed\n");
+ clicon_debug(1, "plugin_exit() failed");
return -1;
}
if (dlclose(cp->cp_handle) != 0) {
@@ -385,7 +387,7 @@ clixon_plugin_auth(clicon_handle h,
if ((authfn = cp->cp_api.ca_auth) == NULL)
continue;
if ((retval = authfn(h, arg)) < 0) {
- clicon_debug(1, "plugin_auth() failed\n");
+ clicon_debug(1, "plugin_auth() failed");
return -1;
}
break;
diff --git a/lib/src/clixon_proto.c b/lib/src/clixon_proto.c
index 2a71992e..17b09614 100644
--- a/lib/src/clixon_proto.c
+++ b/lib/src/clixon_proto.c
@@ -64,14 +64,13 @@
/* 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"
-#include "clixon_xsl.h"
#include "clixon_proto.h"
static int _atomicio_sig = 0;
@@ -246,9 +245,10 @@ atomicio(ssize_t (*fn) (int, void *, size_t),
if (!_atomicio_sig)
continue;
}
- else
- if (errno == EAGAIN)
- continue;
+ else if (errno == EAGAIN)
+ continue;
+ else if (errno == ECONNRESET)/* Connection reset by peer */
+ res = 0;
case 0: /* fall thru */
return (res);
default:
@@ -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 35c4b28c..9de878c0 100644
--- a/lib/src/clixon_proto_client.c
+++ b/lib/src/clixon_proto_client.c
@@ -57,14 +57,13 @@
/* 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"
+#include "clixon_options.h"
#include "clixon_plugin.h"
-#include "clixon_xsl.h"
#include "clixon_string.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
@@ -152,8 +151,14 @@ clicon_rpc_msg(clicon_handle h,
* Want to go over to use netconf directly between client and server,...
* @param[in] h clicon handle
* @param[in] xmlstr XML netconf tree as string
- * @param[out] xret Return XML netconf tree, error or OK
+ * @param[out] xret Return XML netconf tree, error or OK (need to be freed)
* @param[out] sp Socket pointer for notification, otherwise NULL
+ * @code
+ * cxobj *xret = NULL;
+ * if (clicon_rpc_netconf(h, "", &xret, NULL) < 0)
+ * err;
+ * xml_free(xret);
+ * @endcode
* @see clicon_rpc_netconf_xml xml as tree instead of string
*/
int
@@ -182,6 +187,14 @@ clicon_rpc_netconf(clicon_handle h,
* @param[in] xml XML netconf tree
* @param[out] xret Return XML netconf tree, error or OK
* @param[out] sp Socket pointer for notification, otherwise NULL
+ * @code
+ * cxobj *xret = NULL;
+ * int s;
+ * if (clicon_rpc_netconf_xml(h, x, &xret, &s) < 0)
+ * err;
+ * xml_free(xret);
+ * @endcode
+
* @see clicon_rpc_netconf xml as string instead of tree
*/
int
@@ -776,7 +789,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
new file mode 100644
index 00000000..0b516fc4
--- /dev/null
+++ b/lib/src/clixon_stream.c
@@ -0,0 +1,1049 @@
+/*
+ *
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLIXON.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ Alternatively, the contents of this file may be used under the terms of
+ the GNU General Public License Version 3 or later (the "GPL"),
+ in which case the provisions of the GPL are applicable instead
+ of those above. If you wish to allow use of your version of this file only
+ under the terms of the GPL, and not to allow others to
+ use your version of this file under the terms of Apache License version 2,
+ indicate your decision by deleting the provisions above and replace them with
+ the notice and other provisions required by the GPL. If you do not delete
+ the provisions above, a recipient may use your version of this file under
+ the terms of any one of the Apache License version 2 or the GPL.
+
+ ***** END LICENSE BLOCK *****
+
+ * Event notification streams according to RFC5277
+ * The stream implementation has three parts:
+ * 1) Base stream handling: stream_find/register/delete_all/get_xml
+ * 2) Stream subscription handling (stream_ss_add/delete/timeout, stream_notify, etc
+ * 3) Stream replay: stream_replay/_add
+ * 4) nginx/nchan publish code (use --enable-publish config option)
+ *
+ *
+ * +---------------+ 1 arg
+ * | client_entry | <----------------- +---------------+
+ * +---------------+ +-->| subscription |
+ * / +---------------+
+ * +---------------+ * +---------------+
+ * | clicon_handle |--------->| event_stream |
+ * +---------------+ +---------------+
+ * \ * +---------------+
+ * +-->| replay |
+ * +---------------+
+
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "clixon_config.h" /* generated by config & autoconf */
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* cligen */
+#include
+
+/* clicon */
+#include "clixon_queue.h"
+#include "clixon_err.h"
+#include "clixon_log.h"
+#include "clixon_event.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"
+
+/* Go through and timeout subscription timers [s] */
+#define STREAM_TIMER_TIMEOUT_S 5
+
+/*! Find an event notification stream given name
+ * @param[in] h Clicon handle
+ * @param[in] name Name of stream
+ * @retval es Event notification stream structure
+ * @retval NULL Not found
+ */
+event_stream_t *
+stream_find(clicon_handle h,
+ const char *name)
+{
+ event_stream_t *es0;
+ event_stream_t *es = NULL;
+
+ es0 = clicon_stream(h);
+ if ((es = es0) != NULL)
+ do {
+ if (strcmp(name, es->es_name)==0)
+ return es;
+ es = NEXTQ(struct event_stream *, es);
+ } while (es && es != es0);
+ return NULL;
+}
+
+/*! Add notification event stream
+ * @param[in] h Clicon handle
+ * @param[in] name Name of stream
+ * @param[in] description Description of stream
+ * @param[in] replay_enabled Set if replay possible in stream
+ * @param[in] retention For replay buffer how much relative to save
+ */
+int
+stream_add(clicon_handle h,
+ const char *name,
+ const char *description,
+ const int replay_enabled,
+ struct timeval *retention)
+{
+ int retval = -1;
+ event_stream_t *es;
+
+ if ((es = stream_find(h, name)) != NULL)
+ goto ok;
+ if ((es = malloc(sizeof(event_stream_t))) == NULL){
+ clicon_err(OE_XML, errno, "malloc");
+ goto done;
+ }
+ memset(es, 0, sizeof(event_stream_t));
+ if ((es->es_name = strdup(name)) == NULL){
+ clicon_err(OE_XML, errno, "strdup");
+ goto done;
+ }
+ if ((es->es_description = strdup(description)) == NULL){
+ clicon_err(OE_XML, errno, "strdup");
+ goto done;
+ }
+ es->es_replay_enabled = replay_enabled;
+ if (retention)
+ es->es_retention = *retention;
+ clicon_stream_append(h, es);
+ ok:
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Delete complete notification event stream list (not just single stream)
+ * @param[in] es
+ */
+int
+stream_delete_all(clicon_handle h)
+{
+ struct stream_replay *r;
+ struct stream_subscription *ss;
+ event_stream_t *es;
+ event_stream_t *head = clicon_stream(h);
+
+ while ((es = clicon_stream(h)) != NULL){
+ DELQ(es, head, event_stream_t *);
+ clicon_stream_set(h, head);
+ if (es->es_name)
+ free(es->es_name);
+ if (es->es_description)
+ free(es->es_description);
+ while ((ss = es->es_subscription) != NULL)
+ stream_ss_rm(h, es, ss); /* XXX in some cases leaks memory due to DONT clause in stream_ss_rm() */
+ while ((r = es->es_replay) != NULL){
+ DELQ(r, es->es_replay, struct stream_replay *);
+ if (r->r_xml)
+ xml_free(r->r_xml);
+ free(r);
+ }
+ free(es);
+ }
+ return 0;
+}
+
+/*! Return stream definition state in XML supporting RFC 8040 and RFC5277
+ * @param[in] h Clicon handle
+ * @param[in] access If set, include access/location
+ * @param[out] cb Output buffer containing XML on exit
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+int
+stream_get_xml(clicon_handle h,
+ int access,
+ cbuf *cb)
+{
+ event_stream_t *es = NULL;
+ char *url_prefix;
+ char *stream_path;
+
+ cprintf(cb, "");
+ if ((es = clicon_stream(h)) != NULL){
+ do {
+ cprintf(cb, "");
+ cprintf(cb, "%s", es->es_name);
+ if (es->es_description)
+ cprintf(cb, "%s", es->es_description);
+ cprintf(cb, "%s",
+ es->es_replay_enabled?"true":"false");
+ if (access){
+ cprintf(cb, "");
+ cprintf(cb, "xml");
+ url_prefix = clicon_option_str(h, "CLICON_STREAM_URL");
+ stream_path = clicon_option_str(h, "CLICON_STREAM_PATH");
+ cprintf(cb, "%s/%s/%s",
+ url_prefix, stream_path, es->es_name);
+ cprintf(cb, "");
+ }
+ cprintf(cb, "");
+ es = NEXTQ(struct event_stream *, es);
+ } while (es && es != clicon_stream(h));
+ }
+ cprintf(cb, "");
+ return 0;
+}
+
+/*! Check all stream subscription stop timers, set up new timer
+ * @param[in] fd No-op
+ * @param[in] arg Clicon handle
+ * @note format is given by event_reg_timeout callback function (fd not needed)
+ */
+int
+stream_timer_setup(int fd,
+ void *arg)
+{
+ int retval = -1;
+ clicon_handle h = (clicon_handle)arg;
+ struct timeval now;
+ struct timeval t;
+ struct timeval t1 = {STREAM_TIMER_TIMEOUT_S, 0};
+ struct timeval tret;
+ event_stream_t *es;
+ struct stream_subscription *ss;
+ struct stream_subscription *ss1;
+ struct stream_replay *r;
+ struct stream_replay *r1;
+
+ clicon_debug(2, "%s", __FUNCTION__);
+ /* Go thru callbacks and see if any have timed out, if so remove them
+ * Could also be done by a separate timer.
+ */
+ gettimeofday(&now, NULL);
+ /* For all event streams:
+ * 1) Go through subscriptions, if stop-time and its past, remove it
+ * XXX: but client may not be closed
+ * 2) Go throughreplay buffer and remove entries with passed retention time
+ */
+ if ((es = clicon_stream(h)) != NULL){
+ do {
+ /* 1) Go through subscriptions, if stop-time and its past, remove it */
+ if ((ss = es->es_subscription) != NULL)
+ do {
+ if (timerisset(&ss->ss_stoptime) && timercmp(&ss->ss_stoptime, &now, <)){
+ ss1 = NEXTQ(struct stream_subscription *, ss);
+ /* Signal to remove stream for upper levels */
+ if (stream_ss_rm(h, es, ss) < 0)
+ goto done;
+ ss = ss1;
+ }
+ else
+ ss = NEXTQ(struct stream_subscription *, ss);
+ } while (ss && ss != es->es_subscription);
+ /* 2) Go throughreplay buffer and remove entries with passed retention time */
+ if (timerisset(&es->es_retention) &&
+ (r = es->es_replay) != NULL){
+ timersub(&now, &es->es_retention, &tret);
+ do {
+ if (timercmp(&r->r_tv, &tret, <)){
+ r1 = NEXTQ(struct stream_replay *, r);
+ DELQ(r, es->es_replay, struct stream_replay *);
+ if (r->r_xml)
+ xml_free(r->r_xml);
+ free(r);
+ r = r1;
+ }
+ else
+ r = NEXTQ(struct stream_replay *, r);
+ } while (r && r!=es->es_replay);
+ }
+ es = NEXTQ(struct event_stream *, es);
+ } while (es && es != clicon_stream(h));
+ }
+ /* Initiate new timer */
+ timeradd(&now, &t1, &t);
+ if (event_reg_timeout(t,
+ stream_timer_setup, /* this function */
+ h, /* clicon handle */
+ "stream timer setup") < 0)
+ goto done;
+ retval = 0;
+ done:
+ return retval;
+}
+
+#ifdef NYI
+/*! Delete single notification event stream
+ * XXX notused
+ */
+int
+stream_del()
+{
+ return 0;
+}
+#endif
+
+/*! 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] startime If set, Make a replay
+ * @param[in] stoptime If set, dont continue past this time
+ * @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, ie no such stream
+ */
+struct stream_subscription *
+stream_ss_add(clicon_handle h,
+ char *stream,
+ char *xpath,
+ struct timeval *starttime,
+ struct timeval *stoptime,
+ stream_fn_t fn,
+ void *arg)
+{
+ event_stream_t *es;
+ struct stream_subscription *ss = NULL;
+
+ clicon_debug(1, "%s", __FUNCTION__);
+ if ((es = stream_find(h, stream)) == NULL){
+ clicon_err(OE_CFG, ENOENT, "Stream %s not found", stream);
+ goto done;
+ }
+ if ((ss = malloc(sizeof(*ss))) == NULL){
+ clicon_err(OE_CFG, errno, "malloc");
+ goto done;
+ }
+ memset(ss, 0, sizeof(*ss));
+ if ((ss->ss_stream = strdup(stream)) == NULL){
+ clicon_err(OE_CFG, errno, "strdup");
+ goto done;
+ }
+ if (stoptime)
+ ss->ss_stoptime = *stoptime;
+ if (starttime)
+ ss->ss_starttime = *starttime;
+ if (xpath && (ss->ss_xpath = strdup(xpath)) == NULL){
+ clicon_err(OE_CFG, errno, "strdup");
+ goto done;
+ }
+ ss->ss_fn = fn;
+ ss->ss_arg = arg;
+ ADDQ(ss, es->es_subscription);
+ return ss;
+ done:
+ if (ss)
+ free(ss);
+ return NULL;
+}
+
+/*! Delete event stream subscription 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_ss_rm(clicon_handle h,
+ event_stream_t *es,
+ struct stream_subscription *ss)
+{
+ clicon_debug(1, "%s", __FUNCTION__);
+ DELQ(ss, es->es_subscription, struct stream_subscription *);
+ /* Remove from upper layers - close socket etc. */
+ (*ss->ss_fn)(h, 1, NULL, ss->ss_arg);
+#ifdef DONT /* upcall may have deleted it */
+ if (ss->ss_stream)
+ free(ss->ss_stream);
+ if (ss->ss_xpath)
+ free(ss->ss_xpath);
+ free(ss);
+#endif
+ clicon_debug(1, "%s retval: 0", __FUNCTION__);
+ return 0;
+}
+
+/*! Find stream callback given callback function and its (unique) argument
+ * @param[in] es Pointer to event stream
+ * @param[in] fn Stream callback
+ * @param[in] arg Argument - typically unique client handle
+ * @retval ss Event stream subscription structure
+ * @retval NULL Not found
+ */
+struct stream_subscription *
+stream_ss_find(event_stream_t *es,
+ stream_fn_t fn,
+ void *arg)
+{
+ struct stream_subscription *ss;
+
+ if ((ss = es->es_subscription) != NULL)
+ do {
+ if (fn == ss->ss_fn && arg == ss->ss_arg)
+ return ss;
+ ss = NEXTQ(struct stream_subscription *, ss);
+ } while (ss && ss != es->es_subscription);
+ return NULL;
+}
+
+/*! Remove stream subscription identified with fn and arg in all streams
+ * @param[in] h Clicon handle
+ * @param[in] fn Stream callback
+ * @param[in] arg Argument - typically unique client handle
+ * @see stream_ss_delete For single stream
+ */
+int
+stream_ss_delete_all(clicon_handle h,
+ stream_fn_t fn,
+ void *arg)
+{
+ int retval = -1;
+ event_stream_t *es;
+ struct stream_subscription *ss;
+
+ if ((es = clicon_stream(h)) != NULL){
+ do {
+ if ((ss = stream_ss_find(es, fn, arg)) != NULL){
+ if (stream_ss_rm(h, es, ss) < 0)
+ goto done;
+ }
+ es = NEXTQ(struct event_stream *, es);
+ } while (es && es != clicon_stream(h));
+ }
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Delete a single stream
+ * @see stream_ss_delete_all (merge with this?)
+ */
+int
+stream_ss_delete(clicon_handle h,
+ char *name,
+ stream_fn_t fn,
+ void *arg)
+{
+ int retval = -1;
+ event_stream_t *es;
+ struct stream_subscription *ss;
+
+ if ((es = clicon_stream(h)) != NULL){
+ do {
+ if (strcmp(name, es->es_name)==0)
+ if ((ss = stream_ss_find(es, fn, arg)) != NULL){
+ if (stream_ss_rm(h, es, ss) < 0)
+ goto done;
+ }
+ es = NEXTQ(struct event_stream *, es);
+ } while (es && es != clicon_stream(h));
+ }
+ 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] tv Timestamp. Dont notify if subscription has stoptimees_subscription) != NULL)
+ do {
+ if (timerisset(&ss->ss_stoptime) && /* stoptime has passed */
+ timercmp(&ss->ss_stoptime, tv, <)){
+ struct stream_subscription *ss1;
+ ss1 = NEXTQ(struct stream_subscription *, ss);
+ /* Signal to remove stream for upper levels */
+ if (stream_ss_rm(h, es, ss) < 0)
+ goto done;
+ ss = ss1;
+ }
+ else{ /* xpath match */
+ if (ss->ss_xpath == NULL ||
+ strlen(ss->ss_xpath)==0 ||
+ xpath_first(xevent, "%s", ss->ss_xpath) != NULL)
+ if ((*ss->ss_fn)(h, 0, xevent, ss->ss_arg) < 0)
+ goto done;
+ ss = NEXTQ(struct stream_subscription *, ss);
+ }
+ } while (es->es_subscription && ss != es->es_subscription);
+ 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_notify1 Internal
+ */
+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;
+ event_stream_t *es;
+
+ clicon_debug(2, "%s", __FUNCTION__);
+ if ((es = stream_find(h, stream)) == NULL)
+ goto ok;
+ 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_notify1(h, es, &tv, xev) < 0)
+ 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;
+ done:
+ if (cb)
+ cbuf_free(cb);
+ if (xev)
+ xml_free(xev);
+ if (str)
+ free(str);
+ return retval;
+}
+
+/*! Backward compatible function
+ * @param[in] h Clicon handle
+ * @param[in] stream Name of event stream. CLICON is predefined as LOG stream
+ * @param[in] xml Notification as XML stream. Is copied.
+ * @retval 0 OK
+ * @retval -1 Error with clicon_err called
+ * @see stream_notify Should be merged with this
+ */
+int
+stream_notify_xml(clicon_handle h,
+ char *stream,
+ cxobj *xml)
+{
+ int retval = -1;
+ cxobj *xev = NULL;
+ cxobj *xml2; /* copy */
+ yang_spec *yspec = NULL;
+ char *str = NULL;
+ cbuf *cb = NULL;
+ char timestr[27];
+ struct timeval tv;
+ event_stream_t *es;
+
+ clicon_debug(2, "%s", __FUNCTION__);
+ if ((es = stream_find(h, stream)) == NULL)
+ goto ok;
+ 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 ((xml2 = xml_dup(xml)) == NULL)
+ goto done;
+ if (xml_addsub(xev, xml2) < 0)
+ goto done;
+ if (stream_notify1(h, es, &tv, xev) < 0)
+ 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;
+ done:
+ if (cb)
+ cbuf_free(cb);
+ if (xev)
+ xml_free(xev);
+ if (str)
+ free(str);
+ return retval;
+}
+
+
+/*! Replay a stream by sending notification messages
+ * @see RFC5277 Sec 2.1.1:
+ * Start Time:
+ A parameter, , used to trigger the replay feature
+ and indicate that the replay should start at the time
+ specified. If 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 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, , used with the optional
+ replay feature to indicate the newest notifications of
+ interest. If is not present, the notifications will
+ continue until the subscription is terminated. Must be used
+ with and be later than . Values of in
+ the future are valid. This parameter is of type dateTime and
+ compliant to [RFC3339]. Implementations must support time
+ zones.
+
+ * Assume no future sample timestamps.
+ */
+static int
+stream_replay_notify(clicon_handle h,
+ event_stream_t *es,
+ struct stream_subscription *ss)
+{
+ int retval = -1;
+ struct stream_replay *r;
+
+ /* If is not present, this is not a replay */
+ if (!timerisset(&ss->ss_starttime))
+ goto ok;
+ if (!es->es_replay_enabled)
+ goto ok;
+ /* Get replay linked list */
+ if ((r = es->es_replay) == NULL)
+ goto ok;
+ /* First loop to skip until start */
+ do {
+ if (timercmp(&r->r_tv, &ss->ss_starttime, >=))
+ break;
+ r = NEXTQ(struct stream_replay *, r);
+ } while (r && r!=es->es_replay);
+ if (r == NULL)
+ goto ok; /* No samples to replay */
+ /* Then notify until stop */
+ do {
+ if (timerisset(&ss->ss_stoptime) &&
+ timercmp(&r->r_tv, &ss->ss_stoptime, >))
+ break;
+ if ((*ss->ss_fn)(h, 0, r->r_xml, ss->ss_arg) < 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;
+}
+
+/* tmp struct for timeout callback containing clicon handle,
+ * stream and subscription
+ */
+struct replay_arg{
+ clicon_handle ra_h;
+ char *ra_stream; /* Name of stream - malloced: free by cb */
+ stream_fn_t ra_fn; /* Stream callback */
+ void *ra_arg; /* Argument - typically unique client handle */
+};
+
+/*! Timeout callback for replaying stream
+ * @param[in] fd Ignore
+ * @param[in] arg tmp struct including clicon handle, stream and subscription
+ */
+static int
+stream_replay_cb(int fd,
+ void *arg)
+{
+ int retval = -1;
+ struct replay_arg *ra= (struct replay_arg*)arg;
+ event_stream_t *es;
+ struct stream_subscription *ss;
+
+ if (ra == NULL)
+ goto ok;
+ if (ra->ra_stream == NULL)
+ goto ok;
+ if ((es = stream_find(ra->ra_h, ra->ra_stream)) == NULL)
+ goto ok;
+ if ((ss = stream_ss_find(es, ra->ra_fn, ra->ra_arg)) == NULL)
+ goto ok;
+ if (stream_replay_notify(ra->ra_h, es, ss) < 0)
+ goto done;
+ ok:
+ retval = 0;
+ done:
+ if (ra){
+ if (ra->ra_stream)
+ free(ra->ra_stream);
+ free(ra);
+ }
+ return retval;
+}
+
+/*! Schedule stream replay to occur asap, eg "now"
+ *
+ * @param[in] h clicon handle
+ * @param[in] stream Name of stream
+ * @param[in] fn Stream callback
+ * @param[in] arg Argument - typically unique client handle
+ */
+int
+stream_replay_trigger(clicon_handle h,
+ char *stream,
+ stream_fn_t fn,
+ void *arg)
+{
+ int retval = -1;
+ struct timeval now;
+ struct replay_arg *ra;
+
+ if ((ra = malloc(sizeof(*ra))) == NULL){
+ clicon_err(OE_UNIX, errno, "malloc");
+ goto done;
+ }
+ memset(ra, 0, sizeof(*ra));
+ ra->ra_h = h;
+ if ((ra->ra_stream = strdup(stream)) == NULL){
+ clicon_err(OE_UNIX, errno, "strdup");
+ goto done;
+ }
+ ra->ra_fn = fn;
+ ra->ra_arg = arg;
+ gettimeofday(&now, NULL);
+ if (event_reg_timeout(now, stream_replay_cb, ra,
+ "create-subscribtion stream replay") < 0)
+ goto done;
+ retval = 0;
+ done:
+ return retval;
+}
+
+#ifdef CLIXON_PUBLISH_STREAMS
+/* 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
+
+/*
+ * Types (curl)
+ */
+struct curlbuf{
+ size_t b_len;
+ char *b_buf;
+};
+
+/*
+ * For the asynchronous case. I think we must handle the case where of many of these
+ * come in before we can handle them in the upper-level polling routine.
+ * realloc. Therefore, we append new data to the userdata buffer.
+ */
+static size_t
+curl_get_cb(void *ptr,
+ size_t size,
+ size_t nmemb,
+ void *userdata)
+{
+ struct curlbuf *buf = (struct curlbuf *)userdata;
+ int len;
+
+ len = size*nmemb;
+ if ((buf->b_buf = realloc(buf->b_buf, buf->b_len+len+1)) == NULL)
+ return 0;
+ memcpy(buf->b_buf+buf->b_len, ptr, len);
+ buf->b_len += len;
+ buf->b_buf[buf->b_len] = '\0';
+ return len;
+}
+
+/*! Send a curl POST request
+ * @retval -1 fatal error
+ * @retval 0 expect set but did not expected return or other non-fatal error
+ * @retval 1 ok
+ * Note: curl_easy_perform blocks
+ * Note: New handle is created every time, the handle can be re-used for better TCP performance
+ * @see same function (url_post) in grideye_curl.c
+ */
+static int
+url_post(char *url,
+ char *postfields,
+ char **getdata)
+{
+ int retval = -1;
+ CURL *curl = NULL;
+ char *err = NULL;
+ struct curlbuf cb = {0, };
+ CURLcode errcode;
+
+ /* Try it with curl -X PUT -d '*/
+ clicon_debug(1, "%s: curl -X POST -d '%s' %s",
+ __FUNCTION__, postfields, url);
+ /* Set up curl for doing the communication with the controller */
+ if ((curl = curl_easy_init()) == NULL) {
+ clicon_debug(1, "curl_easy_init");
+ goto done;
+ }
+ if ((err = malloc(CURL_ERROR_SIZE)) == NULL) {
+ clicon_debug(1, "%s: malloc", __FUNCTION__);
+ goto done;
+ }
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_get_cb);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &cb);
+ curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, err);
+ curl_easy_setopt(curl, CURLOPT_POST, 1);
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields);
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(postfields));
+
+ if (debug)
+ curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
+ if ((errcode = curl_easy_perform(curl)) != CURLE_OK){
+ clicon_debug(1, "%s: curl: %s(%d)", __FUNCTION__, err, errcode);
+ retval = 0;
+ goto done;
+ }
+ if (getdata && cb.b_buf){
+ *getdata = cb.b_buf;
+ cb.b_buf = NULL;
+ }
+ retval = 1;
+ done:
+ if (err)
+ free(err);
+ if (cb.b_buf)
+ free(cb.b_buf);
+ if (curl)
+ curl_easy_cleanup(curl); /* cleanup */
+ return retval;
+}
+
+/*! Stream callback for example stream notification
+ * Push via curl_post to publish stream event
+ * @param[in] h Clicon handle
+ * @param[in] op Operation: 0 OK, 1 Close
+ * @param[in] event Event as XML
+ * @param[in] arg Extra argument provided in stream_ss_add
+ * @see stream_ss_add
+ */
+static int
+stream_publish_cb(clicon_handle h,
+ int op,
+ cxobj *event,
+ void *arg)
+{
+ int retval = -1;
+ cbuf *u = NULL; /* stream pub (push) url */
+ cbuf *d = NULL; /* (XML) data to push */
+ char *pub_prefix;
+ char *result = NULL;
+ char *stream = (char*)arg;
+
+ clicon_debug(1, "%s", __FUNCTION__);
+ if (op != 0)
+ goto ok;
+ /* Create pub url */
+ if ((u = cbuf_new()) == NULL){
+ clicon_err(OE_XML, errno, "cbuf_new");
+ goto done;
+ }
+ if ((pub_prefix = clicon_option_str(h, "CLICON_STREAM_PUB")) == NULL){
+ clicon_err(OE_CFG, ENOENT, "CLICON_STREAM_PUB not defined");
+ goto done;
+ }
+ cprintf(u, "%s/%s", pub_prefix, stream);
+ /* Create XML data as string */
+ if ((d = cbuf_new()) == NULL){
+ clicon_err(OE_XML, errno, "cbuf_new");
+ goto done;
+ }
+ if (clicon_xml2cbuf(d, event, 0, 0) < 0)
+ goto done;
+ if (url_post(cbuf_get(u), /* url+stream */
+ cbuf_get(d), /* postfields */
+ &result) < 0) /* result as xml */
+ goto done;
+ if (result)
+ clicon_debug(1, "%s: %s", __FUNCTION__, result);
+ ok:
+ retval = 0;
+ done:
+ if (u)
+ cbuf_free(u);
+ if (d)
+ cbuf_free(d);
+ if (result)
+ free(result);
+ return retval;
+}
+#endif /* CLIXON_PUBLISH_STREAMS */
+
+/*! Publish all streams on a pubsub channel, eg using SSE
+ */
+int
+stream_publish(clicon_handle h,
+ char *stream)
+{
+#ifdef CLIXON_PUBLISH_STREAMS
+ int retval = -1;
+
+ if (stream_ss_add(h, stream, NULL, NULL, NULL, stream_publish_cb, (void*)stream) < 0)
+ goto done;
+ retval = 0;
+ done:
+ return retval;
+#else
+ clicon_log(LOG_WARNING, "%s called but CLIXON_PUBLISH_STREAMS not enabled (enable with configure --enable-publish)", __FUNCTION__);
+ clicon_log_init("xpath", LOG_WARNING, CLICON_LOG_STDERR);
+ return 0;
+#endif
+}
+
+int
+stream_publish_init()
+{
+#ifdef CLIXON_PUBLISH_STREAMS
+ int retval = -1;
+
+ if (curl_global_init(CURL_GLOBAL_ALL) != 0){
+ clicon_err(OE_PLUGIN, errno, "curl_global_init");
+ goto done;
+ }
+ retval = 0;
+ done:
+ return retval;
+#else
+ return 0;
+#endif
+}
+
+int
+stream_publish_exit()
+{
+#ifdef CLIXON_PUBLISH_STREAMS
+ curl_global_cleanup();
+#endif
+ return 0;
+}
diff --git a/lib/src/clixon_string.c b/lib/src/clixon_string.c
index 655bc9f1..480805fb 100644
--- a/lib/src/clixon_string.c
+++ b/lib/src/clixon_string.c
@@ -174,49 +174,77 @@ uri_unreserved(unsigned char in)
}
/*! Percent encoding according to RFC 3986 URI Syntax
- * @param[in] str Not-encoded input string
- * @param[out] escp Encoded/escaped malloced output string
+ * @param[out] encp Encoded malloced output string
+ * @param[in] fmt Not-encoded input string (stdarg format string)
+ * @param[in] ... stdarg variable parameters
* @retval 0 OK
* @retval -1 Error
+ * @code
+ * char *enc;
+ * if (uri_percent_encode(&enc, "formatstr: <>= %s", "substr<>") < 0)
+ * err;
+ * if(enc)
+ * free(enc);
+ * @endcode
* @see RFC 3986 Uniform Resource Identifier (URI): Generic Syntax
* @see uri_percent_decode
* @see xml_chardata_encode
*/
int
-uri_percent_encode(char *str,
- char **escp)
+uri_percent_encode(char **encp,
+ char *fmt, ...)
{
- int retval = -1;
- char *esc = NULL;
- int len;
- int i, j;
-
+ int retval = -1;
+ char *str = NULL; /* Expanded format string w stdarg */
+ char *enc = NULL;
+ int fmtlen;
+ int len;
+ int i, j;
+ va_list args;
+
+ /* Two steps: (1) read in the complete format string */
+ va_start(args, fmt); /* dryrun */
+ fmtlen = vsnprintf(NULL, 0, fmt, args) + 1;
+ va_end(args);
+ if ((str = malloc(fmtlen)) == NULL){
+ clicon_err(OE_UNIX, errno, "malloc");
+ goto done;
+ }
+ memset(str, 0, fmtlen);
+ va_start(args, fmt); /* real */
+ fmtlen = vsnprintf(str, fmtlen, fmt, args) + 1;
+ va_end(args);
+ /* Now str is the combined fmt + ... */
+
+ /* Step (2) encode and expand str --> enc */
/* This is max */
len = strlen(str)*3+1;
- if ((esc = malloc(len)) == NULL){
+ if ((enc = malloc(len)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
- memset(esc, 0, len);
+ memset(enc, 0, len);
j = 0;
for (i=0; i 2 &&
- isxdigit(esc[i+1]) && isxdigit(esc[i+2])){
- hstr[0] = esc[i+1];
- hstr[1] = esc[i+2];
+ for (i=0; i 2 &&
+ isxdigit(enc[i+1]) && isxdigit(enc[i+2])){
+ hstr[0] = enc[i+1];
+ hstr[1] = enc[i+2];
hstr[2] = 0;
str[j] = strtoul(hstr, &ptr, 16);
i += 2;
}
else
- str[j] = esc[i];
+ str[j] = enc[i];
j++;
}
str[j++] = '\0';
@@ -265,8 +293,9 @@ uri_percent_decode(char *esc,
}
/*! Escape characters according to XML definition
- * @param[in] str Not-encoded input string
- * @param[out] escp Encoded/escaped malloced output string
+ * @param[out] encp Encoded malloced output string
+ * @param[in] fmt Not-encoded input string (stdarg format string)
+ * @param[in] ... stdarg variable parameters
* @retval 0 OK
* @retval -1 Error
* @see https://www.w3.org/TR/2008/REC-xml-20081126/#syntax chapter 2.6
@@ -274,8 +303,7 @@ uri_percent_decode(char *esc,
* @see AMPERSAND mode in clixon_xml_parse.l
* @code
* char *encstr = NULL;
- * char *val = "a<>b";
- * if (xml_chardata_encode(str, &encstr) < 0)
+ * if (xml_chardata_encode(&encstr, "fmtstr<>& %s", "substr<>") < 0)
* err;
* if (encstr)
* free(encstr);
@@ -289,16 +317,34 @@ uri_percent_decode(char *esc,
* Optionally >
*/
int
-xml_chardata_encode(char *str,
- char **escp)
+xml_chardata_encode(char **escp,
+ char *fmt,...)
{
- int retval = -1;
- char *esc = NULL;
- int l;
- int len;
- int i, j;
- int cdata; /* when set, skip encoding */
+ int retval = -1;
+ char *str = NULL; /* Expanded format string w stdarg */
+ int fmtlen;
+ char *esc = NULL;
+ int l;
+ int len;
+ int i, j;
+ int cdata; /* when set, skip encoding */
+ va_list args;
+ /* Two steps: (1) read in the complete format string */
+ va_start(args, fmt); /* dryrun */
+ fmtlen = vsnprintf(NULL, 0, fmt, args) + 1;
+ va_end(args);
+ if ((str = malloc(fmtlen)) == NULL){
+ clicon_err(OE_UNIX, errno, "malloc");
+ goto done;
+ }
+ memset(str, 0, fmtlen);
+ va_start(args, fmt); /* real */
+ fmtlen = vsnprintf(str, fmtlen, fmt, args) + 1;
+ va_end(args);
+ /* Now str is the combined fmt + ... */
+
+ /* Step (2) encode and expand str --> enc */
/* First compute length (do nothing) */
len = 0; cdata = 0;
for (i=0; i':
- len += strlen("> ");
+ len += strlen(">");
break;
default:
len++;
@@ -349,7 +395,7 @@ xml_chardata_encode(char *str,
else
switch (str[i]){
case '&':
- if ((l=snprintf(&esc[j], 7, "& ")) < 0){
+ if ((l=snprintf(&esc[j], 6, "&")) < 0){
clicon_err(OE_UNIX, errno, "snprintf");
goto done;
}
@@ -361,14 +407,14 @@ xml_chardata_encode(char *str,
cdata++;
break;
}
- if ((l=snprintf(&esc[j], 6, "< ")) < 0){
+ if ((l=snprintf(&esc[j], 5, "<")) < 0){
clicon_err(OE_UNIX, errno, "snprintf");
goto done;
}
j += l;
break;
case '>':
- if ((l=snprintf(&esc[j], 6, "> ")) < 0){
+ if ((l=snprintf(&esc[j], 5, ">")) < 0){
clicon_err(OE_UNIX, errno, "snprintf");
goto done;
}
@@ -381,6 +427,8 @@ xml_chardata_encode(char *str,
*escp = esc;
retval = 0;
done:
+ if (str)
+ free(str);
if (retval < 0 && esc)
free(esc);
return retval;
diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c
index c2393782..9b09d34c 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"
@@ -1018,7 +1018,7 @@ clicon_xml2file(FILE *f,
case CX_BODY:
if ((val = xml_value(x)) == NULL) /* incomplete tree */
break;
- if (xml_chardata_encode(val, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", val) < 0)
goto done;
fprintf(f, "%s", encstr);
break;
@@ -1142,7 +1142,7 @@ clicon_xml2cbuf(cbuf *cb,
case CX_BODY:
if ((val = xml_value(x)) == NULL) /* incomplete tree */
break;
- if (xml_chardata_encode(val, &encstr) < 0)
+ if (xml_chardata_encode(&encstr, "%s", val) < 0)
goto done;
cprintf(cb, "%s", encstr);
break;
@@ -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/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c
index b909385b..4dfe4f96 100644
--- a/lib/src/clixon_xml_db.c
+++ b/lib/src/clixon_xml_db.c
@@ -143,10 +143,8 @@ xmldb_plugin_unload(clicon_handle h)
xmldb_handle xh;
char *error;
- if ((handle = clicon_xmldb_plugin_get(h)) == NULL){
- clicon_err(OE_PLUGIN, errno, "No plugin handle");
- goto done;
- }
+ if ((handle = clicon_xmldb_plugin_get(h)) == NULL)
+ goto ok; /* OK, may not have been initialized */
/* If connected storage handle then disconnect */
if ((xh = clicon_xmldb_handle_get(h)) != NULL)
xmldb_disconnect(h); /* sets xmldb handle to NULL */
@@ -165,8 +163,9 @@ xmldb_plugin_unload(clicon_handle h)
clicon_err(OE_PLUGIN, errno, "dlclose: %s", error ? error : "Unknown error");
/* Just report no -1 return*/
}
+ ok:
retval = 0;
- done:
+ // done:
return retval;
}
@@ -231,6 +230,7 @@ xmldb_disconnect(clicon_handle h)
xmldb_handle xh;
struct xmldb_api *xa;
+ clicon_debug(1, "%s", __FUNCTION__);
if ((xa = clicon_xmldb_api_get(h)) == NULL){
clicon_err(OE_DB, 0, "No xmldb plugin");
goto done;
diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c
index 301dfa0d..d9c394cf 100644
--- a/lib/src/clixon_xml_map.c
+++ b/lib/src/clixon_xml_map.c
@@ -80,12 +80,11 @@
#include "clixon_string.h"
#include "clixon_yang.h"
#include "clixon_yang_type.h"
-#include "clixon_options.h"
#include "clixon_xml.h"
+#include "clixon_options.h"
#include "clixon_plugin.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
-#include "clixon_xsl.h"
#include "clixon_log.h"
#include "clixon_err.h"
#include "clixon_xml_sort.h"
@@ -1007,7 +1006,7 @@ api_path_fmt2api_path(char *api_path_fmt,
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
- if (uri_percent_encode(str, &strenc) < 0)
+ if (uri_percent_encode(&strenc, "%s", str) < 0)
goto done;
cprintf(cb, "%s", strenc);
free(strenc); strenc = NULL;
@@ -1088,13 +1087,7 @@ api_path_fmt2xpath(char *api_path_fmt,
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
- cprintf(cb,
-#ifdef COMPAT_XSL
- "[%s=%s]",
-#else
- "[%s='%s']",
-#endif
- cv_name_get(cv), str);
+ cprintf(cb, "[%s='%s']", cv_name_get(cv), str);
free(str);
}
}
@@ -1511,13 +1504,7 @@ api_path2xpath_cvv(yang_spec *yspec,
cprintf(xpath, "/%s", name);
v = val;
while ((cvi = cvec_each(cvk, cvi)) != NULL){
- cprintf(xpath,
-#ifdef COMPAT_XSL
- "[%s=%s]",
-#else
- "[%s='%s']",
-#endif
- cv_string_get(cvi), v);
+ cprintf(xpath, "[%s='%s']", cv_string_get(cvi), v);
v += strlen(v)+1;
}
if (val)
diff --git a/lib/src/clixon_xml_parse.l b/lib/src/clixon_xml_parse.l
index 57000d0e..4e9b297a 100644
--- a/lib/src/clixon_xml_parse.l
+++ b/lib/src/clixon_xml_parse.l
@@ -89,17 +89,18 @@ int clixon_xml_parsewrap(void)
return NAME; /* rather be catch-all */
}
[ \t]+ ;
-\: return *clixon_xml_parsetext;
+\: return *clixon_xml_parsetext;
\n { _YA->ya_linenum++;}
""/>" { BEGIN(STATEA); return ESLASH; }
"" { BEGIN(START); return ECOMMENT; }
-\n _YA->ya_linenum++;
+\n _YA->ya_linenum++;
.
-encoding return ENC;
-version return VER;
-"=" return *clixon_xml_parsetext;
-"?>" { BEGIN(START);return ETEXT;}
-\" { BEGIN(STRDQ); return *clixon_xml_parsetext; }
-\' { BEGIN(STRSQ); return *clixon_xml_parsetext; }
+encoding return ENC;
+version return VER;
+"=" return *clixon_xml_parsetext;
+"?>" { BEGIN(START);return ETEXT;}
+\" { _YA->ya_lex_state =TEXTDECL;BEGIN(STRDQ); return *clixon_xml_parsetext; }
+\' { _YA->ya_lex_state =TEXTDECL;BEGIN(STRSQ); return *clixon_xml_parsetext; }
-[^\"]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; }
-\" { BEGIN(START); return *clixon_xml_parsetext; }
+1\.[0-9]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; }
+[^\"]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; }
+\" { BEGIN(_YA->ya_lex_state); return *clixon_xml_parsetext; }
-1\.[0-9]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; }
-[^\"]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; }
-\" { BEGIN(TEXTDECL); return *clixon_xml_parsetext; }
-
-1\.[0-9]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; }
-[^\']+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; }
-\' { BEGIN(TEXTDECL); return *clixon_xml_parsetext; }
+1\.[0-9]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; }
+[^\']+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; }
+\' { BEGIN(_YA->ya_lex_state); return *clixon_xml_parsetext; }
%%
diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y
index 8a80ff8d..ef9f78a9 100644
--- a/lib/src/clixon_xml_parse.y
+++ b/lib/src/clixon_xml_parse.y
@@ -401,6 +401,8 @@ attr : NAME '=' attvalue { if (xml_parse_attr(_YA, NULL, $1, $3)
attvalue : '\"' CHARDATA '\"' { $$=$2; /* $2 must be consumed */}
| '\"' '\"' { $$=strdup(""); /* $2 must be consumed */}
+ | '\'' CHARDATA '\'' { $$=$2; /* $2 must be consumed */}
+ | '\'' '\'' { $$=strdup(""); /* $2 must be consumed */}
;
%%
diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c
index e3aa4b23..27842b6a 100644
--- a/lib/src/clixon_xml_sort.c
+++ b/lib/src/clixon_xml_sort.c
@@ -74,12 +74,13 @@
int xml_child_sort = 1;
-/*! Given an XML object and a child name, return yang stmt of child
+/*! Given a child name and an XML object, return yang stmt of child
* If no xml parent, find root yang stmt matching name
- * @param[in] name Name of child
+ * @param[in] x Child
* @param[in] xp XML parent, can be NULL.
* @param[in] yspec Yang specification (top level)
* @param[out] yresult Pointer to yang stmt of result, or NULL, if not found
+ * @note special rule for rpc, ie ,look for top "foo" node.
*/
int
xml_child_spec(char *name,
@@ -108,6 +109,7 @@ xml_child_spec(char *name,
* @retval >0 if arg1 is greater than arg2
* @note args are pointer ot pointers, to fit into qsort cmp function
* @see xml_cmp1 Similar, but for one object
+ * @note empty value/NULL is smallest value
*/
int
xml_cmp(const void* arg1,
@@ -144,7 +146,12 @@ xml_cmp(const void* arg1,
return 0; /* Ordered by user: maintain existing order */
switch (y1->ys_keyword){
case Y_LEAF_LIST: /* Match with name and value */
- equal = strcmp(xml_body(x1), xml_body(x2));
+ if ((b1 = xml_body(x1)) == NULL)
+ equal = -1;
+ else if ((b2 = xml_body(x2)) == NULL)
+ equal = 1;
+ else
+ equal = strcmp(b1, b2);
break;
case Y_LIST: /* Match with key values
* Use Y_LIST cache (see struct yang_stmt)
@@ -168,7 +175,7 @@ xml_cmp(const void* arg1,
}
/*! Compare xml object
- * @param[in] yangi Yang order
+ * @param[in] yangi Yang order
* @param[in] keynr Length of keyvec/keyval vector when applicable
* @param[in] keyvec Array of of yang key identifiers
* @param[in] keyval Array of of yang key values
@@ -203,8 +210,10 @@ xml_cmp1(cxobj *x,
case Y_LEAF_LIST: /* Match with name and value */
if (userorder && yang_find((yang_node*)y, Y_ORDERED_BY, "user") != NULL)
*userorder=1;
- b=xml_body(x);
- match = strcmp(keyval[0], b);
+ if ((b=xml_body(x)) == NULL)
+ match = 1;
+ else
+ match = strcmp(keyval[0], b);
break;
case Y_LIST: /* Match with array of key values */
if (userorder && yang_find((yang_node*)y, Y_ORDERED_BY, "user") != NULL)
diff --git a/lib/src/clixon_xpath.c b/lib/src/clixon_xpath.c
index 173b30e5..2fec113a 100644
--- a/lib/src/clixon_xpath.c
+++ b/lib/src/clixon_xpath.c
@@ -62,7 +62,6 @@
#include "clixon_handle.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
-#include "clixon_xsl.h"
#include "clixon_xpath_parse.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
@@ -89,17 +88,6 @@ const map_str2int xpopmap[] = {
{NULL, -1}
};
-/* Mapping between axis type string <--> int */
-static const map_str2int axismap[] = {
- {"self", A_SELF},
- {"child", A_CHILD},
- {"parent", A_PARENT},
- {"root", A_ROOT},
- {"ancestor", A_ANCESTOR},
- {"descendant-or-self", A_DESCENDANT_OR_SELF},
- {NULL, -1}
-};
-
static const map_str2int xpath_tree_map[] = {
{"expr", XP_EXP},
{"andexpr", XP_AND},
@@ -857,14 +845,14 @@ xp_eval(xp_ctx *xc,
xp_ctx *xr2 = NULL;
int use_xr0 = 0; /* In 2nd child use transitively result of 1st child */
- if (debug){
+ if (debug>1){
cbuf *cb;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
ctx_print(cb, +2, xc, (char*)clicon_int2str(xpath_tree_map, xs->xs_type));
- clicon_debug(1, "%s", cbuf_get(cb));
+ clicon_debug(2, "%s", cbuf_get(cb));
cbuf_free(cb);
}
/* Pre-actions before check first child c0
@@ -1028,7 +1016,7 @@ xp_eval(xp_ctx *xc,
goto done;
}
ctx_print(cb, -2, *xrp, (char*)clicon_int2str(xpath_tree_map, xs->xs_type));
- clicon_debug(1, "%s", cbuf_get(cb));
+ clicon_debug(2, "%s", cbuf_get(cb));
cbuf_free(cb);
}
retval = 0;
@@ -1071,10 +1059,10 @@ xpath_vec_ctx(cxobj *xcur,
clicon_err(OE_XML, 0, "XPATH parser error with no error code (should not happen)");
goto done;
}
- if (debug){
+ if (debug > 1){
cbuf *cb = cbuf_new();
xpath_tree_print(cb, xy.xy_top);
- clicon_debug(1, "xpath parse tree:\n%s", cbuf_get(cb));
+ clicon_debug(2, "xpath parse tree:\n%s", cbuf_get(cb));
cbuf_free(cb);
}
xc.xc_type = XT_NODESET;
@@ -1142,16 +1130,11 @@ xpath_first(cxobj *xcur,
goto done;
}
va_end(ap);
-#ifdef COMPAT_XSL
- if ((cx = xpath_first_xsl(xcur, xpath)) == NULL)
- goto done;
-#else
if (xpath_vec_ctx(xcur, xpath, &xr) < 0)
goto done;
if (xr && xr->xc_type == XT_NODESET && xr->xc_size)
cx = xr->xc_nodeset[0];
-#endif
done:
if (xr)
ctx_free(xr);
@@ -1211,10 +1194,6 @@ xpath_vec(cxobj *xcur,
va_end(ap);
*vec=NULL;
*veclen = 0;
-#ifdef COMPAT_XSL
- if (xpath_vec_xsl(xcur, xpath, vec, veclen) < 0)
- goto done;
-#else
if (xpath_vec_ctx(xcur, xpath, &xr) < 0)
goto done;
if (xr && xr->xc_type == XT_NODESET){
@@ -1222,7 +1201,6 @@ xpath_vec(cxobj *xcur,
xr->xc_nodeset = NULL;
*veclen = xr->xc_size;
}
-#endif
retval = 0;
done:
if (xr)
@@ -1268,10 +1246,8 @@ xpath_vec_flag(cxobj *xcur,
size_t len;
char *xpath = NULL;
xp_ctx *xr = NULL;
-#ifndef COMPAT_XSL
int i;
cxobj *x;
-#endif
va_start(ap, veclen);
len = vsnprintf(NULL, 0, format, ap);
@@ -1291,10 +1267,6 @@ xpath_vec_flag(cxobj *xcur,
va_end(ap);
*vec=NULL;
*veclen = 0;
-#ifdef COMPAT_XSL
- if (xpath_vec_flag_xsl(xcur, xpath, flags, vec, veclen) < 0)
- goto done;
-#else
if (xpath_vec_ctx(xcur, xpath, &xr) < 0)
goto done;
if (xr && xr->xc_type == XT_NODESET){
@@ -1305,8 +1277,6 @@ xpath_vec_flag(cxobj *xcur,
goto done;
}
}
-#endif
-
retval = 0;
done:
if (xr)
diff --git a/lib/src/clixon_xpath_parse.y b/lib/src/clixon_xpath_parse.y
index 0e601167..4e055ca7 100644
--- a/lib/src/clixon_xpath_parse.y
+++ b/lib/src/clixon_xpath_parse.y
@@ -193,85 +193,85 @@ xp_new(enum xp_type type,
/*
*/
-start : expr X_EOF { _XY->xy_top=$1;clicon_debug(1,"start->expr"); YYACCEPT; }
- | locationpath X_EOF { _XY->xy_top=$1;clicon_debug(1,"start->locationpath"); YYACCEPT; }
+start : expr X_EOF { _XY->xy_top=$1;clicon_debug(2,"start->expr"); YYACCEPT; }
+ | locationpath X_EOF { _XY->xy_top=$1;clicon_debug(2,"start->locationpath"); YYACCEPT; }
;
-expr : expr LOGOP andexpr { $$=xp_new(XP_EXP,$2,0.0,NULL,NULL,$1, $3);clicon_debug(1,"expr->expr or andexpr"); }
- | andexpr { $$=xp_new(XP_EXP,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"expr-> andexpr"); }
+expr : expr LOGOP andexpr { $$=xp_new(XP_EXP,$2,0.0,NULL,NULL,$1, $3);clicon_debug(2,"expr->expr or andexpr"); }
+ | andexpr { $$=xp_new(XP_EXP,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(2,"expr-> andexpr"); }
;
-andexpr : andexpr LOGOP relexpr { $$=xp_new(XP_AND,$2,0.0,NULL,NULL,$1, $3);clicon_debug(1,"andexpr-> andexpr and relexpr"); }
- | relexpr { $$=xp_new(XP_AND,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"andexpr-> relexpr"); }
+andexpr : andexpr LOGOP relexpr { $$=xp_new(XP_AND,$2,0.0,NULL,NULL,$1, $3);clicon_debug(2,"andexpr-> andexpr and relexpr"); }
+ | relexpr { $$=xp_new(XP_AND,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(2,"andexpr-> relexpr"); }
;
-relexpr : relexpr RELOP addexpr { $$=xp_new(XP_RELEX,$2,0.0,NULL,NULL,$1, $3);clicon_debug(1,"relexpr-> relexpr relop addexpr"); }
- | addexpr { $$=xp_new(XP_RELEX,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"relexpr-> addexpr"); }
+relexpr : relexpr RELOP addexpr { $$=xp_new(XP_RELEX,$2,0.0,NULL,NULL,$1, $3);clicon_debug(2,"relexpr-> relexpr relop addexpr"); }
+ | addexpr { $$=xp_new(XP_RELEX,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(2,"relexpr-> addexpr"); }
;
-addexpr : addexpr ADDOP unionexpr { $$=xp_new(XP_ADD,$2,0.0,NULL,NULL,$1, $3);clicon_debug(1,"addexpr-> addexpr ADDOP unionexpr"); }
- | unionexpr { $$=xp_new(XP_ADD,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"addexpr-> unionexpr"); }
+addexpr : addexpr ADDOP unionexpr { $$=xp_new(XP_ADD,$2,0.0,NULL,NULL,$1, $3);clicon_debug(2,"addexpr-> addexpr ADDOP unionexpr"); }
+ | unionexpr { $$=xp_new(XP_ADD,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(2,"addexpr-> unionexpr"); }
;
/* node-set */
-unionexpr : unionexpr '|' pathexpr { $$=xp_new(XP_UNION,A_NAN,0.0,NULL,NULL,$1, $3);clicon_debug(1,"unionexpr-> unionexpr | pathexpr"); }
- | pathexpr { $$=xp_new(XP_UNION,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"unionexpr-> pathexpr"); }
+unionexpr : unionexpr '|' pathexpr { $$=xp_new(XP_UNION,A_NAN,0.0,NULL,NULL,$1, $3);clicon_debug(2,"unionexpr-> unionexpr | pathexpr"); }
+ | pathexpr { $$=xp_new(XP_UNION,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(2,"unionexpr-> pathexpr"); }
;
-pathexpr : locationpath { $$=xp_new(XP_PATHEXPR,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"pathexpr-> locationpath"); }
- | primaryexpr { $$=xp_new(XP_PATHEXPR,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"pathexpr-> primaryexpr"); }
+pathexpr : locationpath { $$=xp_new(XP_PATHEXPR,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(2,"pathexpr-> locationpath"); }
+ | primaryexpr { $$=xp_new(XP_PATHEXPR,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(2,"pathexpr-> primaryexpr"); }
;
/* location path returns a node-set */
-locationpath : rellocpath { $$=xp_new(XP_LOCPATH,A_NAN,0.0,NULL,NULL,$1, NULL); clicon_debug(1,"locationpath-> rellocpath"); }
- | abslocpath { $$=xp_new(XP_LOCPATH,A_NAN,0.0,NULL,NULL,$1, NULL); clicon_debug(1,"locationpath-> abslocpath"); }
+locationpath : rellocpath { $$=xp_new(XP_LOCPATH,A_NAN,0.0,NULL,NULL,$1, NULL); clicon_debug(2,"locationpath-> rellocpath"); }
+ | abslocpath { $$=xp_new(XP_LOCPATH,A_NAN,0.0,NULL,NULL,$1, NULL); clicon_debug(2,"locationpath-> abslocpath"); }
;
-abslocpath : '/' { $$=xp_new(XP_ABSPATH,A_ROOT,0.0,NULL,NULL,NULL, NULL);clicon_debug(1,"abslocpath-> /"); }
- | '/' rellocpath { $$=xp_new(XP_ABSPATH,A_ROOT,0.0,NULL,NULL,$2, NULL);clicon_debug(1,"abslocpath->/ rellocpath");}
+abslocpath : '/' { $$=xp_new(XP_ABSPATH,A_ROOT,0.0,NULL,NULL,NULL, NULL);clicon_debug(2,"abslocpath-> /"); }
+ | '/' rellocpath { $$=xp_new(XP_ABSPATH,A_ROOT,0.0,NULL,NULL,$2, NULL);clicon_debug(2,"abslocpath->/ rellocpath");}
/* // is short for /descendant-or-self::node()/ */
- | DOUBLESLASH rellocpath {$$=xp_new(XP_ABSPATH,A_DESCENDANT_OR_SELF,0.0,NULL,NULL,$2, NULL); clicon_debug(1,"abslocpath-> // rellocpath"); }
+ | DOUBLESLASH rellocpath {$$=xp_new(XP_ABSPATH,A_DESCENDANT_OR_SELF,0.0,NULL,NULL,$2, NULL); clicon_debug(2,"abslocpath-> // rellocpath"); }
;
-rellocpath : step { $$=xp_new(XP_RELLOCPATH,A_NAN,0.0,NULL,NULL,$1, NULL); clicon_debug(1,"rellocpath-> step"); }
- | rellocpath '/' step { $$=xp_new(XP_RELLOCPATH,A_NAN,0.0,NULL,NULL,$1, $3);clicon_debug(1,"rellocpath-> rellocpath / step"); }
- | rellocpath DOUBLESLASH step { $$=xp_new(XP_RELLOCPATH,A_DESCENDANT_OR_SELF,0.0,NULL,NULL,$1, $3); clicon_debug(1,"rellocpath-> rellocpath // step"); }
+rellocpath : step { $$=xp_new(XP_RELLOCPATH,A_NAN,0.0,NULL,NULL,$1, NULL); clicon_debug(2,"rellocpath-> step"); }
+ | rellocpath '/' step { $$=xp_new(XP_RELLOCPATH,A_NAN,0.0,NULL,NULL,$1, $3);clicon_debug(2,"rellocpath-> rellocpath / step"); }
+ | rellocpath DOUBLESLASH step { $$=xp_new(XP_RELLOCPATH,A_DESCENDANT_OR_SELF,0.0,NULL,NULL,$1, $3); clicon_debug(2,"rellocpath-> rellocpath // step"); }
;
-step : axisspec nodetest predicates {$$=xp_new(XP_STEP,$1,0.0, NULL, NULL, $2, $3);clicon_debug(1,"step->axisspec(%d) nodetest", $1); }
- | '.' predicates { $$=xp_new(XP_STEP,A_SELF, 0.0,NULL, NULL, NULL, $2); clicon_debug(1,"step-> ."); }
- | DOUBLEDOT predicates { $$=xp_new(XP_STEP, A_PARENT, 0.0,NULL, NULL, NULL, $2); clicon_debug(1,"step-> .."); }
+step : axisspec nodetest predicates {$$=xp_new(XP_STEP,$1,0.0, NULL, NULL, $2, $3);clicon_debug(2,"step->axisspec(%d) nodetest", $1); }
+ | '.' predicates { $$=xp_new(XP_STEP,A_SELF, 0.0,NULL, NULL, NULL, $2); clicon_debug(2,"step-> ."); }
+ | DOUBLEDOT predicates { $$=xp_new(XP_STEP, A_PARENT, 0.0,NULL, NULL, NULL, $2); clicon_debug(2,"step-> .."); }
;
-axisspec : AXISNAME DOUBLECOLON { clicon_debug(1,"axisspec-> AXISNAME(%d) ::", $1); $$=$1;}
- | '@' { $$=A_ATTRIBUTE; clicon_debug(1,"axisspec-> @"); }
- | { clicon_debug(1,"axisspec-> "); $$=A_CHILD;}
+axisspec : AXISNAME DOUBLECOLON { clicon_debug(2,"axisspec-> AXISNAME(%d) ::", $1); $$=$1;}
+ | '@' { $$=A_ATTRIBUTE; clicon_debug(2,"axisspec-> @"); }
+ | { clicon_debug(2,"axisspec-> "); $$=A_CHILD;}
;
-nodetest : '*' { $$=xp_new(XP_NODE,A_NAN,0.0, NULL, NULL, NULL, NULL); clicon_debug(1,"nodetest-> *"); }
- | NAME { $$=xp_new(XP_NODE,A_NAN,0.0, NULL, $1, NULL, NULL); clicon_debug(1,"nodetest-> name(%s)",$1); }
- | NAME ':' NAME { $$=xp_new(XP_NODE,A_NAN,0.0, $1, $3, NULL, NULL);clicon_debug(1,"nodetest-> name(%s) : name(%s)", $1, $3); }
- | NAME ':' '*' { $$=xp_new(XP_NODE,A_NAN,0.0, $1, NULL, NULL, NULL);clicon_debug(1,"nodetest-> name(%s) : *", $1); }
- | NODETYPE '(' ')' { $$=xp_new(XP_NODE_FN,A_NAN,0.0, $1, NULL, NULL, NULL); clicon_debug(1,"nodetest-> nodetype()"); }
+nodetest : '*' { $$=xp_new(XP_NODE,A_NAN,0.0, NULL, NULL, NULL, NULL); clicon_debug(2,"nodetest-> *"); }
+ | NAME { $$=xp_new(XP_NODE,A_NAN,0.0, NULL, $1, NULL, NULL); clicon_debug(2,"nodetest-> name(%s)",$1); }
+ | NAME ':' NAME { $$=xp_new(XP_NODE,A_NAN,0.0, $1, $3, NULL, NULL);clicon_debug(2,"nodetest-> name(%s) : name(%s)", $1, $3); }
+ | NAME ':' '*' { $$=xp_new(XP_NODE,A_NAN,0.0, $1, NULL, NULL, NULL);clicon_debug(2,"nodetest-> name(%s) : *", $1); }
+ | NODETYPE '(' ')' { $$=xp_new(XP_NODE_FN,A_NAN,0.0, $1, NULL, NULL, NULL); clicon_debug(2,"nodetest-> nodetype()"); }
;
/* evaluates to boolean */
-predicates : predicates '[' expr ']' { $$=xp_new(XP_PRED,A_NAN,0.0, NULL, NULL, $1, $3); clicon_debug(1,"predicates-> [ expr ]"); }
- | { $$=xp_new(XP_PRED,A_NAN,0.0, NULL, NULL, NULL, NULL); clicon_debug(1,"predicates->"); }
+predicates : predicates '[' expr ']' { $$=xp_new(XP_PRED,A_NAN,0.0, NULL, NULL, $1, $3); clicon_debug(2,"predicates-> [ expr ]"); }
+ | { $$=xp_new(XP_PRED,A_NAN,0.0, NULL, NULL, NULL, NULL); clicon_debug(2,"predicates->"); }
;
-primaryexpr : '(' expr ')' { $$=xp_new(XP_PRI0,A_NAN,0.0, NULL, NULL, $2, NULL); clicon_debug(1,"primaryexpr-> ( expr )"); }
- | NUMBER { $$=xp_new(XP_PRIME_NR,A_NAN, $1, NULL, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> NUMBER(%lf)", $1); }
- | QUOTE string QUOTE { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, $2, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> \" string \""); }
- | QUOTE QUOTE { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, NULL, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> \" \""); }
- | APOST string APOST { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, $2, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> ' string '"); }
- | APOST APOST { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, NULL, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> ' '"); }
- | FUNCTIONNAME '(' ')' { $$=xp_new(XP_PRIME_FN,A_NAN,0.0, $1, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> functionname ( arguments )"); }
+primaryexpr : '(' expr ')' { $$=xp_new(XP_PRI0,A_NAN,0.0, NULL, NULL, $2, NULL); clicon_debug(2,"primaryexpr-> ( expr )"); }
+ | NUMBER { $$=xp_new(XP_PRIME_NR,A_NAN, $1, NULL, NULL, NULL, NULL);clicon_debug(2,"primaryexpr-> NUMBER(%lf)", $1); }
+ | QUOTE string QUOTE { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, $2, NULL, NULL, NULL);clicon_debug(2,"primaryexpr-> \" string \""); }
+ | QUOTE QUOTE { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, NULL, NULL, NULL, NULL);clicon_debug(2,"primaryexpr-> \" \""); }
+ | APOST string APOST { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, $2, NULL, NULL, NULL);clicon_debug(2,"primaryexpr-> ' string '"); }
+ | APOST APOST { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, NULL, NULL, NULL, NULL);clicon_debug(2,"primaryexpr-> ' '"); }
+ | FUNCTIONNAME '(' ')' { $$=xp_new(XP_PRIME_FN,A_NAN,0.0, $1, NULL, NULL, NULL);clicon_debug(2,"primaryexpr-> functionname ( arguments )"); }
;
/* XXX Adding this between FUNCTIONNAME() breaks parser,..
-arguments : arguments expr { clicon_debug(1,"arguments-> arguments expr"); }
- | { clicon_debug(1,"arguments-> "); }
+arguments : arguments expr { clicon_debug(2,"arguments-> arguments expr"); }
+ | { clicon_debug(2,"arguments-> "); }
;
*/
string : string CHAR {
@@ -279,9 +279,9 @@ string : string CHAR {
$$ = realloc($1, len+strlen($2) + 1);
sprintf($$+len, "%s", $2);
free($2);
- clicon_debug(1,"string-> string CHAR");
+ clicon_debug(2,"string-> string CHAR");
}
- | CHAR { clicon_debug(1,"string-> "); }
+ | CHAR { clicon_debug(2,"string-> "); }
;
diff --git a/lib/src/clixon_xsl.c b/lib/src/clixon_xsl.c
deleted file mode 100644
index 86ab22e8..00000000
--- a/lib/src/clixon_xsl.c
+++ /dev/null
@@ -1,1041 +0,0 @@
-/*
- *
- ***** BEGIN LICENSE BLOCK *****
-
- Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
-
- This file is part of CLIXON.
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- Alternatively, the contents of this file may be used under the terms of
- the GNU General Public License Version 3 or later (the "GPL"),
- in which case the provisions of the GPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of the GPL, and not to allow others to
- use your version of this file under the terms of Apache License version 2, indicate
- your decision by deleting the provisions above and replace them with the
- notice and other provisions required by the GPL. If you do not delete
- the provisions above, a recipient may use your version of this file under
- the terms of any one of the Apache License version 2 or the GPL.
-
- ***** END LICENSE BLOCK *****
-
- * Limited XML XPATH and XSLT functions.
- * NOTE: there is a main function at the end of this file where you can test out
- * different xpath expressions.
- * Look at the end of the file for a test unit program
-
-The code is implemented according to XPATH 1.0:
- https://www.w3.org/TR/xpath-10/
-
-The primary syntactic construct in XPath is the expression. An expression matches
-the production Expr (see https://www.w3.org/TR/xpath-10/#NT-Expr)
- */
-#ifdef HAVE_CONFIG_H
-#include "clixon_config.h" /* generated by config & autoconf */
-#endif
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-/* cligen */
-#include
-
-/* clicon */
-#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_yang.h"
-#include "clixon_xml.h"
-#include "clixon_xpath_ctx.h"
-#include "clixon_xpath.h"
-#include "clixon_xsl.h"
-
-
-/* Constants */
-#define XPATH_VEC_START 128
-
-/*
- * Types
- */
-
-struct searchvec{
- cxobj **sv_v0; /* here is result */
- int sv_v0len;
- cxobj **sv_v1; /* this is tmp storage */
- int sv_v1len;
- int sv_max;
-};
-typedef struct searchvec searchvec;
-
-/* Local types
- */
-
-struct xpath_predicate{
- struct xpath_predicate *xp_next;
- char *xp_expr;
-};
-
-/* XPATH Axis according to https://www.w3.org/TR/xpath-10/#NT-Step
- * Axis ::= AxisSpecifier NodeTest Predicate*
- * Eg "child::
- */
-struct xpath_element{
- struct xpath_element *xe_next;
- enum axis_type xe_type;
- char *xe_prefix; /* eg for namespaces */
- char *xe_str; /* eg for child */
- struct xpath_predicate *xe_predicate; /* eg within [] */
-};
-
-/* Mapping between axis type string <--> int */
-static const map_str2int axismap[] = {
- {"self", A_SELF},
- {"child", A_CHILD},
- {"parent", A_PARENT},
- {"root", A_ROOT},
- {"ancestor", A_ANCESTOR},
- {"descendant-or-self", A_DESCENDANT_OR_SELF},
- {NULL, -1}
-};
-
-static int xpath_split(char *xpathstr, char **pathexpr);
-
-/*! Print xpath structure for debug */
-static int
-xpath_print(FILE *f,
- struct xpath_element *xplist)
-{
- struct xpath_element *xe;
- struct xpath_predicate *xp;
-
- for (xe=xplist; xe; xe=xe->xe_next){
- fprintf(f, "\t:%s %s ", clicon_int2str(axismap, xe->xe_type),
- xe->xe_str?xe->xe_str:"");
- for (xp=xe->xe_predicate; xp; xp=xp->xp_next)
- fprintf(f, "[%s]", xp->xp_expr);
- }
- return 0;
-}
-
-/*! Extract PredicateExpr (Expr) from a Predicate within []
- * @see xpath_expr For evaluation of predicate
- */
-static int
-xpath_parse_predicate(struct xpath_element *xe,
- char *pred)
-{
- int retval = -1;
- struct xpath_predicate *xp;
- char *s;
- int i;
- int len;
-
- len = strlen(pred);
- for (i=len-1; i>=0; i--){ /* -1 since we search for ][ */
- s = &pred[i];
- if (i==0 ||
- (*(s)==']' && *(s+1)=='[')){
- if (i) {
- *(s)= '\0';
- s += 2;
- }
- if ((xp = malloc(sizeof(*xp))) == NULL){
- clicon_err(OE_UNIX, errno, "malloc");
- goto done;
- }
- memset(xp, 0, sizeof(*xp));
- if ((xp->xp_expr = strdup(s)) == NULL){
- clicon_err(OE_XML, errno, "strdup");
- goto done;
- }
- xp->xp_next = xe->xe_predicate;
- xe->xe_predicate = xp;
- }
- }
- retval = 0;
- done:
- return retval;
-}
-
-/*! XPATH parse, create new child element
- * @param[in] atype Axis type, see https://www.w3.org/TR/xpath-10/#axes
- * @param[in] str
- * @param[out] xpnext
- */
-static int
-xpath_element_new(enum axis_type atype,
- char *str,
- struct xpath_element ***xpnext)
-{
- int retval = -1;
- struct xpath_element *xe;
- char *str1 = NULL;
- char *pred;
- char *local;
-
- if ((xe = malloc(sizeof(*xe))) == NULL){
- clicon_err(OE_UNIX, errno, "malloc");
- goto done;
- }
- memset(xe, 0, sizeof(*xe));
- xe->xe_type = atype;
- if (str){
- if ((str1 = strdup(str)) == NULL){
- clicon_err(OE_XML, errno, "strdup");
- goto done;
- }
- if (xpath_split(str1, &pred) < 0) /* Can be more predicates */
- goto done;
- if (strlen(str1)){
- /* Split into prefix and localname */
- if ((local = index(str1, ':')) != NULL){
- *local = '\0';
- local++;
- if ((xe->xe_prefix = strdup(str1)) == NULL){
- clicon_err(OE_XML, errno, "strdup");
- goto done;
- }
- }
- else
- local = str1;
- if ((xe->xe_str = strdup(local)) == NULL){
- clicon_err(OE_XML, errno, "strdup");
- goto done;
- }
- }
- else{
- if ((xe->xe_str = strdup("*")) == NULL){
- clicon_err(OE_XML, errno, "strdup");
- goto done;
- }
- }
- if (pred && strlen(pred)){
- if (xpath_parse_predicate(xe, pred) < 0)
- goto done;
- }
- }
- (**xpnext) = xe;
- *xpnext = &xe->xe_next;
- retval = 0;
- done:
- if (str1)
- free(str1);
- return retval;
-}
-
-static int
-xpath_element_free(struct xpath_element *xe)
-{
- struct xpath_predicate *xp;
-
- if (xe->xe_str)
- free(xe->xe_str);
- if (xe->xe_prefix)
- free(xe->xe_prefix);
- while ((xp = xe->xe_predicate) != NULL){
- xe->xe_predicate = xp->xp_next;
- if (xp->xp_expr)
- free(xp->xp_expr);
- free(xp);
- }
- free(xe);
- return 0;
-}
-
-static int
-xpath_free(struct xpath_element *xplist)
-{
- struct xpath_element *xe, *xe_next;
-
- for (xe=xplist; xe; xe=xe_next){
- xe_next = xe->xe_next;
- xpath_element_free(xe);
- }
- return 0;
-}
-
-/*! Parse xpath to xpath_element structure
-
- *
- * [1] LocationPath ::= RelativeLocationPath
- * | AbsoluteLocationPath
- * [2] AbsoluteLocationPath ::= '/' RelativeLocationPath?
- * | AbbreviatedAbsoluteLocationPath
- * [3] RelativeLocationPath ::= Step
- | RelativeLocationPath '/' Step
- | AbbreviatedRelativeLocationPath
- * @see https://www.w3.org/TR/xpath-10/#NT-LocationPath
- */
-static int
-xpath_parse(char *xpath,
- struct xpath_element **xplist0)
-{
- int retval = -1;
- int nvec = 0;
- char *s;
- char *s0;
- int i;
- struct xpath_element *xplist = NULL;
- struct xpath_element **xpnext = &xplist;
- int esc = 0;
-
- if ((s0 = strdup(xpath)) == NULL){
- clicon_err(OE_XML, errno, "strdup");
- goto done;
- }
- s = s0;
- if (strlen(s))
- nvec = 1;
- /* Chop up LocationPath in Steps delimited by '/' (unless [] predicate)
- * Eg, "/a/b[/c]/d" -> "a" "b[/c]" "d"
- */
- esc = 0;
- while (*s != '\0'){
- switch (*s){
- case '/':
- if (esc)
- break;
- nvec++;
- *s = '\0';
- break;
- case '[':
- esc++;
- break;
- case ']':
- esc--;
- break;
- default:
- break;
- }
- s++;
- }
- /* Iterate through steps (s), see https://www.w3.org/TR/xpath-10/#NT-Step */
- s = s0;
- for (i=0; i=
- * -
- * - = # RelationalExpr '=' RelationalExpr
- * - =current() XXX
- * @see https://www.w3.org/TR/xpath/#predicates
- */
-static int
-xpath_expr(cxobj *xcur,
- char *predicate_expression,
- uint16_t flags,
- cxobj ***vec0,
- size_t *vec0len)
-{
- char *e_a;
- char *e_v;
- int i;
- int j;
- int retval = -1;
- cxobj *x;
- cxobj *xv;
- cxobj **vec = NULL;
- size_t veclen = 0;
- int oplen;
- char *tag;
- char *val;
- char *e0;
- char *e;
- char *name;
-
- if ((e0 = strdup(predicate_expression)) == NULL){
- clicon_err(OE_UNIX, errno, "strdup");
- goto done;
- }
- e = e0;
- if (*e == '@'){ /* @ attribute */
- e++;
- e_v=e;
- e_a = strsep(&e_v, "=");
- if (e_a == NULL){
- clicon_err(OE_XML, errno, "malformed expression: [@%s]", e);
- goto done;
- }
- for (i=0; i<*vec0len; i++){
- xv = (*vec0)[i];
- if ((x = xml_find(xv, e_a)) != NULL &&
- (xml_type(x) == CX_ATTR)){
- if (!e_v || strcmp(xml_value(x), e_v) == 0){
- clicon_debug(2, "%s %x %x", __FUNCTION__, flags, xml_flag(xv, flags));
- if (flags==0x0 || xml_flag(xv, flags)){
- if (cxvec_append(xv, &vec, &veclen) < 0)
- goto done;
- break; /* xv added */
- }
- }
- }
- }
- }
- else{ /* either or , where ='=' for now */
- oplen = strcspn(e, "=");
- if (strlen(e+oplen)==0){ /* no operator */
- if (sscanf(e, "%d", &i) == 1){ /* number */
- if (i < *vec0len){
- xv = (*vec0)[i]; /* XXX: cant compress: gcc breaks */
- clicon_debug(2, "%s %x %x", __FUNCTION__, flags, xml_flag(xv, flags));
- if (flags==0x0 || xml_flag(xv, flags))
- if (cxvec_append(xv, &vec, &veclen) < 0)
- goto done;
- }
- }
- else{
- clicon_err(OE_XML, errno, "malformed expression: [%s]", e);
- goto done;
- }
- }
- else{ /* name = expr */
- if ((tag = strsep(&e, "=")) == NULL){
- clicon_err(OE_XML, errno, "malformed expression: [%s]", e);
- goto done;
- }
- /* Strip trailing spaces */
- while (tag[strlen(tag)-1] == ' ')
- tag[strlen(tag)-1] = '\0';
- /* Strip heading spaces */
- while (e[0]==' ')
- e++;
- if (strncmp(e, "current()", strlen("current()")) == 0){
- /* name = current()xpath */
- cxobj **svec0 = NULL;
- size_t svec0len = 0;
- cxobj **svec1 = NULL;
- size_t svec1len = 0;
- char *ebody;
-
- e += strlen("current("); /* e is path expression */
- *e = '.';
- if ((svec0 = calloc(1, sizeof(cxobj *))) == NULL){
- clicon_err(OE_UNIX, errno, "calloc");
- goto done;
- }
- svec0[0] = xcur;
- svec0len++;
- /* Recursive invocation */
- if (xpath_exec(xcur, e, svec0, svec0len,
- flags, &svec1, &svec1len) < 0)
- goto done;
- for (j=0; jxe_type), xe->xe_str?xe->xe_str:"");
-#endif
- switch (xe->xe_type){
- case A_SELF:
- break;
- case A_PARENT:
- j = 0;
- for (i=0; ixe_str, CX_ELMNT, flags, &vec1, &vec1len) < 0)
- goto done;
- }
- }
- else{
- for (i=0; ixe_str, name, 0) == 0) {
- clicon_debug(2, "%s %x %x", __FUNCTION__, flags, xml_flag(x, flags));
- if (flags==0x0 || xml_flag(x, flags))
- if (cxvec_append(x, &vec1, &vec1len) < 0)
- goto done;
- }
- }
- }
- }
- free(vec0);
- vec0 = vec1;
- vec0len = vec1len;
- break;
- case A_DESCENDANT_OR_SELF:
- /* Instead of collecting all descendants (which we could)
- just set a flag and treat that in the next operation */
- descendants++;
- break;
- default:
- break;
- }
- /* remove duplicates
- * This is cycle-heavy and I dont know when it is needed?
- */
- if (0)
- for (i=0; ixe_predicate; xp; xp = xp->xp_next){
- if (xpath_expr(xcur, xp->xp_expr, flags, &vec0, &vec0len) < 0)
- goto done;
- }
-
- if (xpath_find(xcur, xe->xe_next, descendants,
- vec0, vec0len, flags,
- vec2, vec2len) < 0)
- goto done;
- retval = 0;
- done:
- return retval;
-}
-
-/*! Transform eg "a/b[kalle]" -> "a/b" e="kalle"
- * @param[in,out] xpathstr Eg "a/b[kalle]" -> "a/b"
- * @param[out] pathexpr Eg "kalle"
- * Which also means:
- * "a/b[foo][bar]" -> pathexpr: "foo][bar"
- * @note destructively modify xpathstr, no new strings allocated
- */
-static int
-xpath_split(char *xpathstr,
- char **pathexpr)
-{
- int retval = -1;
- int last;
- char *pe = NULL;
-
- if (strlen(xpathstr)){
- last = strlen(xpathstr) - 1; /* XXX: this could be -1.. */
- if (xpathstr[last] == ']'){
- xpathstr[last] = '\0';
- if (strlen(xpathstr)){
- if ((pe = index(xpathstr,'[')) != NULL){
- *pe = '\0';
- pe++;
- }
- }
- if (pe==NULL){
- clicon_err(OE_XML, errno, "mismatched []: %s", xpathstr);
- goto done;
- }
- }
- }
- retval = 0;
- done:
- *pathexpr = pe;
- return retval;
-}
-
-/*! Process single xpath expression on xml tree
- * @param[in] xcur xml-tree where to search
- * @param[in] xpath string with XPATH syntax
- * @param[in] vec0 vector of XML trees
- * @param[in] vec0len length of XML trees
- * @param[in] flags if != 0, only match xml nodes matching flags
- * @param[out] vec2 Result XML node vector
- * @param[out] vec2len Length of result vector.
- * @see https://www.w3.org/TR/xpath-10/#NT-LocationPath
- */
-static int
-xpath_exec(cxobj *xcur,
- char *xpath,
- cxobj **vec0,
- size_t vec0len,
- uint16_t flags,
- cxobj ***vec2,
- size_t *vec2len)
-{
- struct xpath_element *xplist;
- cxobj **vec1;
- size_t vec1len;
-
- if (cxvec_dup(vec0, vec0len, &vec1, &vec1len) < 0)
- goto done;
- if (xpath_parse(xpath, &xplist) < 0)
- goto done;
- if (debug > 1)
- xpath_print(stderr, xplist);
- if (xpath_find(xcur, xplist, 0, vec1, vec1len, flags, vec2, vec2len) < 0)
- goto done;
- if (xpath_free(xplist) < 0)
- goto done;
- done:
- return 0;
-} /* xpath_exec */
-
-
-/*! Intermediate xpath function to handle 'conditional' cases.
- * @param[in] xcur xml-tree where to search
- * @param[in] xpath string with XPATH syntax
- * @param[in] flags if != 0, only match xml nodes matching flags
- * @param[out] vec1 vector of XML trees
- * @param[out] vec1len length of XML trees
- * For example: xpath = //a | //b.
- * xpath_first+ splits xpath up in several subcalls
- * (eg xpath=//a and xpath=//b) and collects the results.
- * Note: if a match is found in both, two (or more) same results will be
- * returned.
- * Note, this could be 'folded' into xpath1 but I judged it too complex.
- * @see https://www.w3.org/TR/xpath-10/#NT-Expr
- * An 'Expr' is composed of compositions of and, or, =, +, -, down to:
- * PathExpr ::= LocationPath
- * | FilterExpr
- * | FilterExpr '/' RelativeLocationPath
- * | FilterExpr '//' RelativeLocationPath
- */
-static int
-xpath_choice(cxobj *xcur,
- char *xpath0,
- uint16_t flags,
- cxobj ***vec1,
- size_t *vec1len)
-{
- int retval = -1;
- char *s0 = NULL;
- char *s1;
- char *s2;
- char *xpath;
- cxobj **vec0 = NULL;
- size_t vec0len = 0;
-
- if ((s0 = strdup(xpath0)) == NULL){
- clicon_err(OE_XML, errno, "strdup");
- goto done;
- }
- s2 = s1 = s0;
- if ((vec0 = calloc(1, sizeof(cxobj *))) == NULL){
- clicon_err(OE_UNIX, errno, "calloc");
- goto done;
- }
- vec0[0] = xcur;
- vec0len++;
- while (s1 != NULL){
- s2 = strstr(s1, " | ");
- if (s2 != NULL){
- *s2 = '\0'; /* terminate xpath */
- s2 += 3;
- }
- xpath = s1;
- s1 = s2;
- if (xpath_exec(xcur, xpath, vec0, vec0len, flags, vec1, vec1len) < 0)
- goto done;
- }
- retval = 0;
- done:
- if (s0)
- free(s0);
- if (vec0)
- free(vec0);
- return retval;
-}
-
-/*! Xpath nodeset function where only the first matching entry is returned
- * args:
- * @param[in] xcur xml-tree where to search
- * @param[in] xpath string with XPATH syntax
- * @retval xml-tree of first match
- * @retval NULL Error or not found
- *
- * @code
- * cxobj *x;
- * if ((x = xpath_first(xtop, "//symbol/foo")) != NULL) {
- * ...
- * }
- * @endcode
- * @note the returned pointer points into the original tree so should not be freed fter use.
- * @note return value does not see difference between error and not found
- * @see xpath_first. This is obsolete and only enabled with COMPAT_XSL
- */
-cxobj *
-xpath_first_xsl(cxobj *xcur,
- char *xpath)
-{
- cxobj **vec1 = NULL;
- size_t vec1len = 0;
- cxobj *xn = NULL;
-
- if (xpath_choice(xcur, xpath, 0, &vec1, &vec1len) < 0)
- goto done;
- if (vec1len)
- xn = vec1[0];
- else
- xn = NULL;
- done:
- if (vec1)
- free(vec1);
- return xn;
-}
-
-#ifdef COMPAT_XSL
-/*! A restricted xpath iterator that loops over all matching entries. Dont use.
- *
- * See xpath1() on details for subset.
- * @param[in] xcur xml-tree where to search
- * @param[in] xpath string with XPATH syntax
- * @param[in] xprev iterator/result should be initiated to NULL
- * @retval xml-tree of n:th match, or NULL on error.
- *
- * @code
- * cxobj *x = NULL;
- * while ((x = xpath_each(xcur, "//symbol/foo", x)) != NULL) {
- * ...
- * }
- * @endcode
- *
- * @note The returned pointer points into the original tree so should not be freed
- * after use.
- * @note obsolete. Dont use
- */
-cxobj *
-xpath_each(cxobj *xcur,
- char *xpath,
- cxobj *xprev)
-{
- static cxobj **vec1 = NULL; /* XXX */
- static size_t vec1len = 0;
- cxobj *xn = NULL;
- int i;
-
- if (xprev == NULL){
- if (vec1) {
- free(vec1);
- vec1 = NULL;
- }
- vec1len = 0;
- if (xpath_choice(xcur, xpath, 0, &vec1, &vec1len) < 0)
- goto done;
- }
- if (vec1len){
- if (xprev==NULL)
- xn = vec1[0];
- else{
- for (i=0; i=vec1len-1)
- xn = NULL;
- else
- xn = vec1[i+1];
- }
- }
- else
- xn = NULL;
- done:
- return xn;
-}
-#endif
-
-/*! A restricted xpath that returns a vector of matches
- *
- * See xpath1() on details for subset
- * @param[in] xcur xml-tree where to search
- * @param[in] xpath string with XPATH syntax
- * @param[out] vec vector of xml-trees. Vector must be free():d after use
- * @param[out] veclen returns length of vector in return value
- * @retval 0 OK
- * @retval -1 error.
- *
- * @code
- * cxobj **xvec = NULL;
- * size_t xlen;
- * if (xpath_vec(xcur, "//symbol/foo", &xvec, &xlen) < 0)
- * goto err;
- * for (i=0; iyp_len; i++){
- ys = ysp->yp_stmt[i];
- switch (class){
- case YC_NONE:
- if ((yc = yang_find((yang_node*)ys, 0, argument)) != NULL)
- return yc;
- break;
- case YC_DATANODE:
- if ((yc = yang_find_datanode((yang_node*)ys, argument)) != NULL)
- return yc;
- break;
- case YC_SCHEMANODE:
- if ((yc = yang_find_schemanode((yang_node*)ys, argument)) != NULL)
- return yc;
- break;
- case YC_DATADEFINITION:
- break; /* nyi */
+ if (yang_nodeid_split(nodeid, &prefix, &id) < 0)
+ goto done;
+ if (prefix){
+ if ((ymod = yang_find((yang_node*)ysp, Y_MODULE, prefix)) != NULL){
+ if ((yres = yang_find((yang_node*)ymod, 0, id)) != NULL)
+ goto ok;
+ goto done;
}
}
- return NULL;
+ else /* No prefix given - loop through and find first */
+ for (i=0; iyp_len; i++){
+ ymod = ysp->yp_stmt[i];
+ switch (class){
+ case YC_NONE:
+ if ((yres = yang_find((yang_node*)ymod, 0, id)) != NULL)
+ goto ok;
+ break;
+ case YC_DATANODE:
+ if ((yres = yang_find_datanode((yang_node*)ymod, id)) != NULL)
+ goto ok;
+ break;
+ case YC_SCHEMANODE:
+ if ((yres = yang_find_schemanode((yang_node*)ymod, id)) != NULL)
+ goto ok;
+ break;
+ case YC_DATADEFINITION:
+ break; /* nyi */
+ }
+ }
+ ok:
+ done:
+ if (prefix)
+ free(prefix);
+ if (id)
+ free(id);
+ return yres;
}
/*! Given a yang statement, find the prefix associated to this module
* @param[in] ys Yang statement
* @retval NULL Not found
* @retval prefix Prefix as char* pointer into yang tree
+ * @code
+ * char *myprefix;
+ * myprefix = yang_find_myprefix(ys);
+ * @endcode
*/
char *
yang_find_myprefix(yang_stmt *ys)
@@ -738,7 +761,8 @@ yang_key2str(int keyword)
return (char*)clicon_int2str(ykmap, keyword);
}
-/*! Find top module or sub-module. Note that ultimate top is yang spec
+/*! Find top module or sub-module given a statement. Ultimate top is yang spec
+ * The routine recursively finds ancestors.
* @param[in] ys Any yang statement in a yang tree
* @retval ymod The top module or sub-module
* @see ys_spec
@@ -822,13 +846,21 @@ yarg_prefix(yang_stmt *ys)
return prefix;
}
-
/*! Split yang node identifier into prefix and identifer.
* @param[in] node-id
* @param[out] prefix Malloced string. May be NULL.
* @param[out] id Malloced identifier.
* @retval 0 OK
* @retval -1 Error
+ * @code
+ * char *prefix = NULL;
+ * char *id = NULL;
+ * if (yang_nodeid_split(nodeid, &prefix, &id) < 0)
+ * goto done;
+ * if (prefix)
+ * free(prefix);
+ * if (id)
+ * free(id);
* @note caller need to free id and prefix after use
*/
int
@@ -1251,11 +1283,11 @@ ys_populate_type(yang_stmt *ys,
* Do this recursively
* @param[in] ys The yang identity to populate.
* @param[in] arg If set contains a derived identifier
- * @see validate_identityref which in runtime validates actual valoues
+ * @see validate_identityref which in runtime validates actual values
*/
static int
ys_populate_identity(yang_stmt *ys,
- void *arg)
+ char *idref)
{
int retval = -1;
yang_stmt *yc = NULL;
@@ -1265,7 +1297,6 @@ ys_populate_identity(yang_stmt *ys,
char *baseid;
char *prefix = NULL;
cbuf *cb = NULL;
- char *idref = (char*)arg;
char *p;
if (idref == NULL){
@@ -1325,31 +1356,91 @@ ys_populate_identity(yang_stmt *ys,
return retval;
}
+/*! Populate yang feature statement - set cv to 1 if enabled
+ *
+ * @param[in] ys Feature yang statement to populate.
+ * @param[in] h Clicon handle
+ */
+static int
+ys_populate_feature(clicon_handle h,
+ yang_stmt *ys)
+{
+ int retval = -1;
+ cxobj *x;
+ yang_stmt *ymod;
+ int found = 0;
+ cg_var *cv;
+ char *module;
+ char *feature;
+ cxobj *x1;
+
+ /* Eg, when parsing the config xml itself */
+ if ((x = clicon_conf_xml(h)) == NULL)
+ goto ok;
+ if ((ymod = ys_module(ys)) == NULL){
+ clicon_err(OE_YANG, 0, "module not found");
+ goto done;
+ }
+ module = ymod->ys_argument;
+ feature = ys->ys_argument;
+ x1 = NULL;
+ while ((x1 = xml_child_each(x, x1, CX_ELMNT)) != NULL && found == 0) {
+ char *m = NULL;
+ char *f = NULL;
+ if (strcmp(xml_name(x1), "CLICON_FEATURE") != 0)
+ continue;
+ /* get m and f from configuration feature rules */
+ if (yang_nodeid_split(xml_body(x1), &m, &f) < 0)
+ goto done;
+ if (m && f &&
+ (strcmp(m,"*")==0 ||
+ strcmp(m, module)==0) &&
+ (strcmp(f,"*")==0 ||
+ strcmp(f, feature)==0))
+ found = 1;
+ if (m) free(m);
+ if (f) free(f);
+ }
+ if ((cv = cv_new(CGV_BOOL)) == NULL){
+ clicon_err(OE_YANG, errno, "cv_new");
+ goto done;
+ }
+ cv_name_set(cv, feature);
+ cv_bool_set(cv, found);
+ clicon_debug(1, "%s %s:%s %d", __FUNCTION__, module, feature, found);
+ ys->ys_cv = cv;
+ ok:
+ retval = 0;
+ done:
+ return retval;
+}
+
/*! Populate with cligen-variables, default values, etc. Sanity checks on complete tree.
*
* We do this in 2nd pass after complete parsing to be sure to have a complete parse-tree
* See ys_parse_sub for first pass and what can be assumed
* After this pass, cv:s are set for LEAFs and LEAF-LISTs
*/
-static int
+int
ys_populate(yang_stmt *ys,
void *arg)
{
int retval = -1;
-
+ // clicon_handle h = (clicon_handle)arg;
+
switch(ys->ys_keyword){
case Y_LEAF:
case Y_LEAF_LIST:
- if (ys_populate_leaf(ys, arg) < 0)
+ if (ys_populate_leaf(ys, NULL) < 0)
goto done;
break;
case Y_LIST:
- if (ys_populate_list(ys, arg) < 0)
+ if (ys_populate_list(ys, NULL) < 0)
goto done;
break;
case Y_RANGE:
case Y_LENGTH:
- if (ys_populate_range(ys, arg) < 0)
+ if (ys_populate_range(ys, NULL) < 0)
goto done;
break;
case Y_MANDATORY: /* call yang_mandatory() to check if set */
@@ -1358,11 +1449,11 @@ ys_populate(yang_stmt *ys,
goto done;
break;
case Y_TYPE:
- if (ys_populate_type(ys, arg) < 0)
+ if (ys_populate_type(ys, NULL) < 0)
goto done;
break;
case Y_IDENTITY:
- if (ys_populate_identity(ys, arg) < 0)
+ if (ys_populate_identity(ys, NULL) < 0)
goto done;
break;
default:
@@ -1373,7 +1464,6 @@ ys_populate(yang_stmt *ys,
return retval;
}
-
/*! Resolve a grouping name from a point in the yang tree
* @retval 0 OK, but ygrouping determines if a grouping was resolved or not
* @retval -1 Error, with clicon_err called
@@ -1594,8 +1684,9 @@ yang_expand(yang_node *yn)
* @retval NULL Error encountered
* Calling order:
* yang_parse # Parse top-level yang module. Expand and populate yang tree
- * yang_parse_recurse # Parse one yang module, go through its (sub)modules, parse them and then recursively parse them
- * yang_parse_file # Read yang file into a string
+ * yang_parse_recurse # Parse one yang module, go through its (sub)modules, parse them and then recursively parse them
+ * yang_parse_filename # Read yang file into a string
+ * yang_parse_file # Read yang open file descriptor into a string
* yang_parse_str # Set up yacc parser and call it given a string
* clixon_yang_parseparse # Actual yang parsing using yacc
*/
@@ -1647,7 +1738,7 @@ yang_parse_str(char *str,
/*! Parse yang spec from an open file descriptor
* @param[in] fd File descriptor containing the YANG file as ASCII characters
* @param[in] name For debug, eg filename
- * @param[in] ysp Yang specification. Should ave been created by caller using yspec_new
+ * @param[in] ysp Yang specification. Should have been created by caller using yspec_new
* @retval ymod Top-level yang (sub)module
* @retval NULL Error
*/
@@ -1695,49 +1786,6 @@ yang_parse_file(int fd,
return ymod; /* top-level (sub)module */
}
-/*! Open a file, read into a string and invoke yang parsing
- *
- * Similar to clicon_yang_str(), just read a file first
- * (cloned from cligen)
- * @param[in] h CLICON handle
- * @param[in] filename Name of file
- * @param[in] ysp Yang specification. Should ave been created by caller using yspec_new
- * @retval ymod Top-level yang (sub)module
- * @retval NULL Error encountered
-
- * The database symbols are inserted in alphabetical order.
- * Calling order:
- * yang_parse # Parse top-level yang module. Expand and populate yang tree
- * yang_parse_recurse # Parse one yang module, go through its (sub)modules, parse them and then recursively parse them
- * yang_parse_file # Read yang file into a string
- * yang_parse_str # Set up yacc parser and call it given a string
- * clixon_yang_parseparse # Actual yang parsing using yacc
- */
-static yang_stmt *
-yang_parse_filename(const char *filename,
- yang_spec *ysp)
-{
- yang_stmt *ymod = NULL;
- int fd = -1;
- struct stat st;
-
- clicon_log(LOG_DEBUG, "Parsing yang file: %s", filename);
- if (stat(filename, &st) < 0){
- clicon_err(OE_YANG, errno, "%s not found", filename);
- goto done;
- }
- if ((fd = open(filename, O_RDONLY)) < 0){
- clicon_err(OE_YANG, errno, "open(%s)", filename);
- goto done;
- }
- if ((ymod = yang_parse_file(fd, filename, ysp)) < 0)
- goto done;
- done:
- if (fd != -1)
- close(fd);
- return ymod; /* top-level (sub)module */
-}
-
/*! No specific revision give. Match a yang file given dir and module
* @param[in] h CLICON handle
* @param[in] yang_dir Directory where all YANG module files reside
@@ -1788,6 +1836,84 @@ yang_parse_find_match(const char *yang_dir,
return retval;
}
+
+/*! Open a file, read into a string and invoke yang parsing
+ *
+ * Similar to clicon_yang_str(), just read a file first
+ * (cloned from cligen)
+ * @param[in] h CLICON handle
+ * @param[in] filename Name of file
+ * @param[in] ysp Yang specification. Should ave been created by caller using yspec_new
+ * @retval ymod Top-level yang (sub)module
+ * @retval NULL Error encountered
+
+ * The database symbols are inserted in alphabetical order.
+ * Calling order:
+ * yang_parse # Parse top-level yang module. Expand and populate yang tree
+ * yang_parse_recurse # Parse one yang module, go through its (sub)modules, parse them and then recursively parse them
+ * yang_parse_filename # Read yang file into a string
+ * yang_parse_file # Read yang open file descriptor into a string
+ * yang_parse_str # Set up yacc parser and call it given a string
+ * clixon_yang_parseparse # Actual yang parsing using yacc
+ */
+static yang_stmt *
+yang_parse_filename(const char *filename,
+ yang_spec *ysp)
+{
+ yang_stmt *ymod = NULL;
+ int fd = -1;
+ struct stat st;
+
+ clicon_log(LOG_DEBUG, "Parsing yang file: %s", filename);
+ if (stat(filename, &st) < 0){
+ clicon_err(OE_YANG, errno, "%s not found", filename);
+ goto done;
+ }
+ if ((fd = open(filename, O_RDONLY)) < 0){
+ clicon_err(OE_YANG, errno, "open(%s)", filename);
+ goto done;
+ }
+ if ((ymod = yang_parse_file(fd, filename, ysp)) < 0)
+ goto done;
+ done:
+ if (fd != -1)
+ close(fd);
+ return ymod; /* top-level (sub)module */
+}
+
+static yang_stmt *
+yang_parse_module(const char *module,
+ const char *dir,
+ const char *revision,
+ yang_spec *ysp)
+{
+ cbuf *fbuf = NULL;
+ int nr;
+ yang_stmt *ymod = NULL;
+
+ if ((fbuf = cbuf_new()) == NULL){
+ clicon_err(OE_YANG, errno, "cbuf_new");
+ goto done;
+ }
+ if (revision)
+ cprintf(fbuf, "%s/%s@%s.yang", dir, module, revision);
+ else{
+ /* No specific revision, Match a yang file */
+ if ((nr = yang_parse_find_match(dir, module, fbuf)) < 0)
+ goto done;
+ if (nr == 0){
+ clicon_err(OE_YANG, errno, "No matching %s yang files found in %s (expected module name or absolute filename)", module, dir);
+ goto done;
+ }
+ }
+ if ((ymod = yang_parse_filename(cbuf_get(fbuf), ysp)) == NULL)
+ goto done;
+ done:
+ if (fbuf)
+ cbuf_free(fbuf);
+ return ymod; /* top-level (sub)module */
+}
+
/*! Parse one yang module then go through (sub)modules and parse them recursively
*
* @param[in] h CLICON handle
@@ -1801,68 +1927,45 @@ yang_parse_find_match(const char *yang_dir,
* Calling order:
* yang_parse # Parse top-level yang module. Expand and populate yang tree
* yang_parse_recurse # Parse one yang module, go through its (sub)modules, parse them and then recursively parse them
- * yang_parse_file # Read yang file into a string
+ * yang_parse_filename # Read yang file into a string
+ * yang_parse_file # Read yang open file descriptor into a string
* yang_parse_str # Set up yacc parser and call it given a string
* clixon_yang_parseparse # Actual yang parsing using yacc
*/
-static yang_stmt *
-yang_parse_recurse(const char *yang_dir,
- const char *module,
- const char *revision,
+static int
+yang_parse_recurse(yang_stmt *ymod,
+ const char *dir,
yang_spec *ysp)
{
+ int retval = -1;
yang_stmt *yi = NULL; /* import */
- yang_stmt *ymod = NULL;
yang_stmt *yrev;
- char *modname;
+ char *submodule;
char *subrevision;
- cbuf *fbuf = NULL;
- int nr;
-
- if (module[0] == '/'){
- if ((ymod = yang_parse_filename(module, ysp)) == NULL)
- goto done;
- }
- else {
- if ((fbuf = cbuf_new()) == NULL){
- clicon_err(OE_YANG, errno, "cbuf_new");
- goto done;
- }
- if (revision)
- cprintf(fbuf, "%s/%s@%s.yang", yang_dir, module, revision);
- else{
- /* No specific revision, Match a yang file */
- if ((nr = yang_parse_find_match(yang_dir, module, fbuf)) < 0)
- goto done;
- if (nr == 0){
- clicon_err(OE_YANG, errno, "No matching %s yang files found in %s (expected module name or absolute filename)", module, yang_dir);
- goto done;
- }
- }
- if ((ymod = yang_parse_filename(cbuf_get(fbuf), ysp)) == NULL)
- goto done;
- }
+ yang_stmt *subymod;
/* go through all import statements of ysp (or its module) */
while ((yi = yn_each((yang_node*)ymod, yi)) != NULL){
if (yi->ys_keyword != Y_IMPORT)
continue;
- modname = yi->ys_argument;
+ submodule = yi->ys_argument;
if ((yrev = yang_find((yang_node*)yi, Y_REVISION_DATE, NULL)) != NULL)
subrevision = yrev->ys_argument;
else
subrevision = NULL;
- if (yang_find((yang_node*)ysp, Y_MODULE, modname) == NULL)
+ if (yang_find((yang_node*)ysp, Y_MODULE, submodule) == NULL){
/* recursive call */
- if (yang_parse_recurse(yang_dir, modname, subrevision, ysp) == NULL){
+ if ((subymod = yang_parse_module(submodule, dir, subrevision, ysp)) == NULL)
+ goto done;
+ if (yang_parse_recurse(subymod, dir, ysp) < 0){
ymod = NULL;
goto done;
}
+ }
}
- done:
- if (fbuf)
- cbuf_free(fbuf);
- return ymod; /* top-level (sub)module */
+ retval = 0;
+ done:
+ return retval; /* top-level (sub)module */
}
int
@@ -1908,13 +2011,92 @@ ys_schemanode_check(yang_stmt *ys,
return retval;
}
+/*! Find feature and if-feature nodes, check features and remove disabled nodes
+ * @retval -1 Error
+ * @retval 0 Feature not enabled: remove yt
+ * @retval 1 OK
+ * @note On return 1 the over-lying function need to remove yt from its parent
+ * @note cannot use yang_apply here since child-list is modified (destructive)
+ */
+static int
+yang_features(clicon_handle h,
+ yang_stmt *yt)
+{
+ int retval = -1;
+ int i;
+ int j;
+ yang_stmt *ys = NULL;
+ char *prefix = NULL;
+ char *feature = NULL;
+ yang_stmt *ymod; /* module yang node */
+ yang_stmt *yfeat; /* feature yang node */
+
+ i = 0;
+ while (iys_len){ /* Note, children may be removed */
+ ys = yt->ys_stmt[i];
+ if (ys->ys_keyword == Y_IF_FEATURE){
+ if (yang_nodeid_split(ys->ys_argument, &prefix, &feature) < 0)
+ goto done;
+ /* Specifically need to handle? strcmp(prefix, myprefix)) */
+ if (prefix == NULL)
+ ymod = ys_module(ys);
+ else
+ ymod = yang_find_module_by_prefix(yt, prefix);
+
+ /* Check if feature exists, and is set, otherwise remove */
+ if ((yfeat = yang_find((yang_node*)ymod, Y_FEATURE, feature)) == NULL ||
+ yfeat->ys_cv == NULL || !cv_bool_get(yfeat->ys_cv)){
+ retval = 0; /* feature not enabled */
+ goto done;
+ }
+ if (prefix){
+ free(prefix);
+ prefix = NULL;
+ }
+ if (feature){
+ free(feature);
+ feature = NULL;
+ }
+ }
+ else
+ if (ys->ys_keyword == Y_FEATURE){
+ if (ys_populate_feature(h, ys) < 0)
+ goto done;
+ } else switch (yang_features(h, ys)){
+ case -1: /* error */
+ goto done;
+ break;
+ case 0: /* disabled: remove ys */
+ for (j=i+1; jys_len; j++)
+ yt->ys_stmt[j-1] = yt->ys_stmt[j];
+ yt->ys_len--;
+ yt->ys_stmt[yt->ys_len] = NULL;
+ ys_free(ys);
+ continue; /* Don't increment i */
+ break;
+ default: /* ok */
+ break;
+ }
+ i++;
+ }
+ retval = 1;
+ done:
+ if (prefix)
+ free(prefix);
+ if (feature)
+ free(feature);
+ return retval;
+}
+
/*! Parse top yang module including all its sub-modules. Expand and populate yang tree
*
* @param[in] h CLICON handle
* @param[in] yang_dir Directory where all YANG module files reside (except mainfile)
- * @param[in] mainmod Name of main YANG module. Or absolute file name.
+ * @param[in] filename File name containing Yang specification. Overrides module
+ * @param[in] module Name of main YANG module. Or absolute file name.
* @param[in] revision Main module revision date string or NULL
- * @param[out] ysp Yang specification. Should ave been created by caller using yspec_new
+ * @param[in,out] ysp Yang specification. Should have been created by caller using yspec_new
+ * @param[out] ymodp Yang module of first, topmost Yang module, if given.
* @retval 0 Everything OK
* @retval -1 Error encountered
* The database symbols are inserted in alphabetical order.
@@ -1924,53 +2106,79 @@ ys_schemanode_check(yang_stmt *ys,
* yang_parse # Parse top-level yang module. Expand and populate yang tree
* yang_parse_recurse # Parse one yang module, go through its (sub)modules,
* parse them and then recursively parse them
- * yang_parse_file # Read yang file into a string
+ * yang_parse_filename # Read yang file into a string
+ * yang_parse_file # Read yang open file descriptor into a string
* yang_parse_str # Set up yacc parser and call it given a string
* clixon_yang_parseparse # Actual yang parsing using yacc
*/
int
yang_parse(clicon_handle h,
- const char *yang_dir,
- const char *mainmodule,
+ const char *filename,
+ const char *module,
+ const char *dir,
const char *revision,
- yang_spec *ysp)
+ yang_spec *ysp,
+ yang_stmt **ymodp)
{
int retval = -1;
- yang_stmt *ymod; /* Top-level yang (sub)module */
+ yang_stmt *ymod = NULL; /* Top-level yang (sub)module */
+ int i;
+ int modnr; /* Existing number of modules */
+ modnr = ysp->yp_len;
+ if (filename){
+ if ((ymod = yang_parse_filename(filename, ysp)) == NULL)
+ goto done;
+ }
+ else
+ if ((ymod = yang_parse_module(module, dir, revision, ysp)) == NULL)
+ goto done;
+
+ /* From here on, apply actions on new modules, ie ones after modnr. */
/* Step 1: parse from text to yang parse-tree. */
- if ((ymod = yang_parse_recurse(yang_dir, mainmodule, revision, ysp)) == NULL)
- goto done;
- /* Add top module name as dbspec-name */
- clicon_dbspec_name_set(h, ymod->ys_argument);
-
- /* Step 2: Go through parse tree and populate it with cv types */
- if (yang_apply((yang_node*)ysp, -1, ys_populate, NULL) < 0)
+ /* Iterate through modules */
+ if (yang_parse_recurse(ymod, dir, ysp) < 0)
goto done;
- /* Step 3: Resolve all types: populate type caches. Requires eg length/range cvecs
+ /* Step 2: check features: check if enabled and remove disabled features */
+ for (i=modnr; iyp_len; i++) /* XXX */
+ if (yang_features(h, ysp->yp_stmt[i]) < 0)
+ goto done;
+
+ /* Step 3: Go through parse tree and populate it with cv types */
+ for (i=modnr; iyp_len; i++)
+ if (yang_apply((yang_node*)ysp->yp_stmt[i], -1, ys_populate, (void*)h) < 0)
+ goto done;
+
+
+ /* Step 4: Resolve all types: populate type caches. Requires eg length/range cvecs
* from ys_populate step
*/
- yang_apply((yang_node*)ysp, Y_TYPE, ys_resolve_type, NULL);
+ for (i=modnr; iyp_len; i++)
+ yang_apply((yang_node*)ysp->yp_stmt[i], Y_TYPE, ys_resolve_type, NULL);
/* Up to here resolving is made in the context they are defined, rather than the
context they are used. Like static scoping. After this we expand all
grouping/uses and unfold all macros into a single tree as they are used.
*/
- /* Step 4: Macro expansion of all grouping/uses pairs. Expansion needs marking */
- if (yang_expand((yang_node*)ysp) < 0)
- goto done;
- yang_apply((yang_node*)ymod, -1, ys_flag_reset, (void*)YANG_FLAG_MARK);
+ /* Step 5: Macro expansion of all grouping/uses pairs. Expansion needs marking */
+ for (i=modnr; iyp_len; i++){
+ if (yang_expand((yang_node*)ysp->yp_stmt[i]) < 0)
+ goto done;
+ yang_apply((yang_node*)ysp->yp_stmt[i], -1, ys_flag_reset, (void*)YANG_FLAG_MARK);
+ }
- /* Step 4: Top-level augmentation of all modules */
+ /* Step 6: Top-level augmentation of all modules XXX: only new modules? */
if (yang_augment_spec(ysp) < 0)
goto done;
- /* sanity check of schemanode references, need more here */
- if (yang_apply((yang_node*)ysp, -1, ys_schemanode_check, NULL) < 0)
- goto done;
-
+ /* Step 7: sanity check of schemanode references, need more here */
+ for (i=modnr; iyp_len; i++)
+ if (yang_apply((yang_node*)ysp->yp_stmt[i], -1, ys_schemanode_check, NULL) < 0)
+ goto done;
+ if (ymodp)
+ *ymodp = ymod;
retval = 0;
done:
return retval;
@@ -2388,55 +2596,83 @@ yang_config(yang_stmt *ys)
return 1;
}
-/*! Parse netconf yang spec, used by netconf client and as internal protocol */
-yang_spec *
-yang_spec_netconf(clicon_handle h)
+/*! Parse yang specification and its dependencies recursively given module
+ * @param[in] h clicon handle
+ * @param[in] module Module name, or absolute filename (including dir)
+ * @param[in] dir Directory where to look for modules and sub-modules
+ * @param[in] revision Revision, or NULL
+ * @param[in,out] yspec Modules parse are added to this yangspec
+ * @param[out] ymodp Yang module of first, topmost Yang module, if given.
+ * @retval 0 OK
+ * @retval -1 Error
+ * @see yang_spec_parse_file
+ */
+int
+yang_spec_parse_module(clicon_handle h,
+ char *module,
+ char *dir,
+ char *revision,
+ yang_spec *yspec,
+ yang_stmt **ymodp)
{
- yang_spec *yspec = NULL;
-
- if ((yspec = yspec_new()) == NULL)
- goto done;
- if (yang_parse(h, CLIXON_DATADIR, "ietf-netconf", NULL, yspec) < 0){
- yspec_free(yspec); yspec = NULL;
+ int retval = -1;
+
+ if (yspec == NULL){
+ clicon_err(OE_YANG, EINVAL, "yang spec is NULL");
goto done;
}
- clicon_netconf_yang_set(h, yspec);
- done:
- return yspec;
+ if (module == NULL){
+ clicon_err(OE_YANG, EINVAL, "yang module not set");
+ goto done;
+ }
+ /* Sanity check - use yang_spec_parse_file instead */
+ if (strlen(module) && module[0] == '/'){
+ clicon_err(OE_YANG, EINVAL, "yang module illegal format");
+ goto done;
+ }
+ if (dir == NULL){
+ clicon_err(OE_YANG, EINVAL, "yang dir not set");
+ goto done;
+ }
+ if (yang_parse(h, NULL, module, dir, revision, yspec, ymodp) < 0)
+ goto done;
+ retval = 0;
+ done:
+ return retval;
}
-/*! Read, parse and save application yang specification as option
- * @param[in] h clicon handle
- * @param[in] f file to print to (if printspec enabled)
- * @param[in] printspec print database (YANG) specification as read from file
+/*! Parse yang specification and its dependencies recursively given filename
+ * @param[in] h clicon handle
+ * @param[in] filename Actual filename (including dir and revision)
+ * @param[in] dir Directory for sub-modules
+ * @param[in,out] yspec Modules parse are added to this yangspec
+ * @param[out] ymodp Yang module of first, topmost Yang module, if given.
+ * @retval 0 OK
+ * @retval -1 Error
+ * @see yang_spec_parse_module for yang dir,module,revision instead of actual filename
*/
-yang_spec*
-yang_spec_main(clicon_handle h)
+int
+yang_spec_parse_file(clicon_handle h,
+ char *filename,
+ char *dir,
+ yang_spec *yspec,
+ yang_stmt **ymodp)
{
- yang_spec *yspec = NULL;
- char *yang_dir;
- char *yang_module;
- char *yang_revision;
-
- if ((yang_dir = clicon_yang_dir(h)) == NULL){
- clicon_err(OE_FATAL, 0, "CLICON_YANG_DIR option not set");
+ int retval = -1;
+
+ if (yspec == NULL){
+ clicon_err(OE_YANG, EINVAL, "yang spec is NULL");
goto done;
}
- /* Yang module is either specific absolute filename, or main module */
- if ((yang_module = clicon_yang_module_main(h)) == NULL){
- clicon_err(OE_FATAL, 0, "CLICON_YANG_MODULE_MAIN option not set");
+ if (dir == NULL){
+ clicon_err(OE_YANG, EINVAL, "yang dir not set");
goto done;
}
- yang_revision = clicon_yang_module_revision(h);
- if ((yspec = yspec_new()) == NULL)
+ if (yang_parse(h, filename, NULL, dir, NULL, yspec, ymodp) < 0)
goto done;
- if (yang_parse(h, yang_dir, yang_module, yang_revision, yspec) < 0){
- yspec_free(yspec); yspec = NULL;
- goto done;
- }
- clicon_dbspec_yang_set(h, yspec);
+ retval = 0;
done:
- return yspec;
+ return retval;
}
/*! Given a yang node, translate the argument string to a cv vector
diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c
new file mode 100644
index 00000000..cf2f3208
--- /dev/null
+++ b/lib/src/clixon_yang_module.c
@@ -0,0 +1,242 @@
+/*
+ *
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLIXON.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ Alternatively, the contents of this file may be used under the terms of
+ the GNU General Public License Version 3 or later (the "GPL"),
+ in which case the provisions of the GPL are applicable instead
+ of those above. If you wish to allow use of your version of this file only
+ under the terms of the GPL, and not to allow others to
+ use your version of this file under the terms of Apache License version 2,
+ indicate your decision by deleting the provisions above and replace them with
+ the notice and other provisions required by the GPL. If you do not delete
+ the provisions above, a recipient may use your version of this file under
+ the terms of any one of the Apache License version 2 or the GPL.
+
+ ***** END LICENSE BLOCK *****
+
+ * Yang module and feature handling
+ * @see https://tools.ietf.org/html/rfc7895
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "clixon_config.h" /* generated by config & autoconf */
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* cligen */
+#include
+
+/* clicon */
+#include "clixon_log.h"
+#include "clixon_err.h"
+#include "clixon_string.h"
+#include "clixon_queue.h"
+#include "clixon_hash.h"
+#include "clixon_handle.h"
+#include "clixon_file.h"
+#include "clixon_yang.h"
+#include "clixon_xml.h"
+#include "clixon_options.h"
+#include "clixon_netconf_lib.h"
+#include "clixon_yang_module.h"
+
+/*! Init the Yang module library
+ *
+ * Load RFC7895 yang spec, module-set-id, etc.
+ * @param[in] h Clicon handle
+ * @note CLIXON_DATADIR is hardcoded
+ */
+int
+yang_modules_init(clicon_handle h)
+{
+ int retval = -1;
+ yang_spec *yspec;
+
+ yspec = clicon_dbspec_yang(h);
+ if (!clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895"))
+ goto ok;
+ /* Ensure module-set-id is set */
+ if (!clicon_option_exists(h, "CLICON_MODULE_SET_ID")){
+ clicon_err(OE_CFG, ENOENT, "CLICON_MODULE_SET_ID must be defined when CLICON_MODULE_LIBRARY_RFC7895 is enabled");
+ goto done;
+ }
+ /* Ensure revision exists is set */
+ if (yang_spec_parse_module(h, "ietf-yang-library", CLIXON_DATADIR, NULL, yspec, NULL)< 0)
+ goto done;
+ /* Find revision */
+ if (yang_modules_revision(h) == NULL){
+ clicon_err(OE_CFG, ENOENT, "Yang client library yang spec has no revision");
+ goto done;
+ }
+ ok:
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Return RFC7895 revision (if parsed)
+ * @param[in] h Clicon handle
+ * @retval revision String (dont free)
+ * @retval NULL Error: RFC7895 not loaded or revision not found
+ */
+char *
+yang_modules_revision(clicon_handle h)
+{
+ yang_spec *yspec;
+ yang_stmt *ymod;
+ yang_stmt *yrev;
+ char *revision = NULL;
+
+ yspec = clicon_dbspec_yang(h);
+ if ((ymod = yang_find((yang_node*)yspec, Y_MODULE, "ietf-yang-library")) != NULL){
+ if ((yrev = yang_find((yang_node*)ymod, Y_REVISION, NULL)) != NULL){
+ revision = yrev->ys_argument;
+ }
+ }
+ return revision;
+}
+
+/*! Get modules state according to RFC 7895
+ * @param[in] h Clicon handle
+ * @param[in] yspec Yang spec
+ * @param[in,out] xret Existing XML tree, merge x into this
+ * @retval -1 Error (fatal)
+ * @retval 0 OK
+ * @retval 1 Statedata callback failed
+ * @notes NYI: schema, deviation
+x +--ro modules-state
+x +--ro module-set-id string
+x +--ro module* [name revision]
+x +--ro name yang:yang-identifier
+x +--ro revision union
+ +--ro schema? inet:uri
+x +--ro namespace inet:uri
+ +--ro feature* yang:yang-identifier
+ +--ro deviation* [name revision]
+ | +--ro name yang:yang-identifier
+ | +--ro revision union
+ +--ro conformance-type enumeration
+ +--ro submodule* [name revision]
+ +--ro name yang:yang-identifier
+ +--ro revision union
+ +--ro schema? inet:uri
+ * @see netconf_create_hello
+ */
+int
+yang_modules_state_get(clicon_handle h,
+ yang_spec *yspec,
+ cxobj **xret)
+{
+ int retval = -1;
+ cxobj *x = NULL;
+ cbuf *cb = NULL;
+ yang_stmt *ylib = NULL; /* ietf-yang-library */
+ yang_stmt *yns = NULL; /* namespace */
+ yang_stmt *ymod; /* generic module */
+ yang_stmt *ys;
+ yang_stmt *yc;
+ char *module_set_id;
+ char *module = "ietf-yang-library";
+
+ module_set_id = clicon_option_str(h, "CLICON_MODULE_SET_ID");
+ if ((ylib = yang_find((yang_node*)yspec, Y_MODULE, module)) == NULL){
+ clicon_err(OE_YANG, 0, "%s not found", module);
+ goto done;
+ }
+ if ((yns = yang_find((yang_node*)ylib, Y_NAMESPACE, NULL)) == NULL){
+ clicon_err(OE_YANG, 0, "%s yang namespace not found", module);
+ goto done;
+ }
+ if ((cb = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, 0, "clicon buffer");
+ goto done;
+ }
+ cprintf(cb,"", yns->ys_argument);
+ cprintf(cb,"%s", module_set_id);
+
+ ymod = NULL;
+ while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) {
+ if (ymod->ys_keyword != Y_MODULE)
+ continue;
+ cprintf(cb,"");
+ cprintf(cb,"%s", ymod->ys_argument);
+ if ((ys = yang_find((yang_node*)ymod, Y_REVISION, NULL)) != NULL)
+ cprintf(cb,"%s", ys->ys_argument);
+ else
+ cprintf(cb,"");
+ if ((ys = yang_find((yang_node*)ymod, Y_NAMESPACE, NULL)) != NULL)
+ cprintf(cb,"%s", ys->ys_argument);
+ else
+ cprintf(cb,"");
+ cprintf(cb, "implement");
+ yc = NULL;
+ while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) {
+ switch(yc->ys_keyword){
+ case Y_FEATURE:
+ if (yc->ys_cv && cv_bool_get(yc->ys_cv))
+ cprintf(cb,"%s", yc->ys_argument);
+ break;
+ case Y_SUBMODULE:
+ cprintf(cb,"");
+ cprintf(cb,"%s", yc->ys_argument);
+ if ((ys = yang_find((yang_node*)yc, Y_REVISION, NULL)) != NULL)
+ cprintf(cb,"%s", ys->ys_argument);
+ else
+ cprintf(cb,"");
+ cprintf(cb,"");
+ break;
+ default:
+ break;
+ }
+ }
+ cprintf(cb,"");
+ }
+ cprintf(cb,"");
+
+ if (xml_parse_string(cbuf_get(cb), yspec, &x) < 0){
+ if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0)
+ goto done;
+ retval = 1;
+ goto done;
+ }
+ retval = netconf_trymerge(x, yspec, xret);
+ done:
+ if (cb)
+ cbuf_free(cb);
+ if (x)
+ xml_free(x);
+ return retval;
+}
diff --git a/lib/src/clixon_yang_parse.l b/lib/src/clixon_yang_parse.l
index 8c65cbd1..d059993a 100644
--- a/lib/src/clixon_yang_parse.l
+++ b/lib/src/clixon_yang_parse.l
@@ -111,6 +111,7 @@ clixon_yang_parsewrap(void)
\} { return *yytext; }
/* RFC 6020 keywords */
+action { BEGIN(ARGUMENT); return K_ACTION; }
anyxml { BEGIN(ARGUMENT); return K_ANYXML; }
argument { BEGIN(ARGUMENT); return K_ARGUMENT; }
augment { BEGIN(ARGUMENT); return K_AUGMENT; }
diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y
index 2dc7d8f2..78cb172f 100644
--- a/lib/src/clixon_yang_parse.y
+++ b/lib/src/clixon_yang_parse.y
@@ -64,6 +64,7 @@
* - Dont want to expose a generated yacc file to the API
* - Cant use the symbols in this file because yacc needs token definitions
*/
+%token K_ACTION
%token K_ANYXML
%token K_ARGUMENT
%token K_AUGMENT
@@ -417,6 +418,7 @@ submodule_header_stmts : submodule_header_stmts submodule_header_stmt
submodule_header_stmt : yang_version_stmt
{ clicon_debug(2,"submodule-header-stmt -> yang-version-stmt"); }
+ | belongs_to_stmt { clicon_debug(2,"submodule-header-stmt -> belongs-to-stmt"); }
;
/* linkage */
@@ -485,6 +487,7 @@ body_stmt : extension_stmt { clicon_debug(2,"body-stmt -> extension-st
| data_def_stmt { clicon_debug(2,"body-stmt -> data-def-stmt");}
| augment_stmt { clicon_debug(2,"body-stmt -> augment-stmt");}
| rpc_stmt { clicon_debug(2,"body-stmt -> rpc-stmt");}
+ | notification_stmt { clicon_debug(2,"body-stmt -> notification-stmt");}
;
data_def_stmt : container_stmt { clicon_debug(2,"data-def-stmt -> container-stmt");}
@@ -521,7 +524,9 @@ container_substmt : when_stmt { clicon_debug(2,"container-substmt -> when-
| reference_stmt { clicon_debug(2,"container-substmt -> reference-stmt"); }
| typedef_stmt { clicon_debug(2,"container-substmt -> typedef-stmt"); }
| grouping_stmt { clicon_debug(2,"container-substmt -> grouping-stmt"); }
- | data_def_stmt { clicon_debug(2,"container-substmt -> data-def-stmt");}
+ | data_def_stmt { clicon_debug(2,"container-substmt -> data-def-stmt");}
+ | action_stmt { clicon_debug(2,"container-substmt -> action-stmt");}
+ | notification_stmt { clicon_debug(2,"container-substmt -> notification-stmt");}
| unknown_stmt { clicon_debug(2,"container-substmt -> unknown-stmt");}
| { clicon_debug(2,"container-substmt ->");}
;
@@ -619,6 +624,8 @@ list_substmt : when_stmt { clicon_debug(2,"list-substmt -> when-stmt
| typedef_stmt { clicon_debug(2,"list-substmt -> typedef-stmt"); }
| grouping_stmt { clicon_debug(2,"list-substmt -> grouping-stmt"); }
| data_def_stmt { clicon_debug(2,"list-substmt -> data-def-stmt"); }
+ | action_stmt { clicon_debug(2,"list-substmt -> action-stmt"); }
+ | notification_stmt { clicon_debug(2,"list-substmt -> notification-stmt"); }
| unknown_stmt { clicon_debug(2,"list-substmt -> unknown-stmt");}
| { clicon_debug(2,"list-substmt -> "); }
;
@@ -792,7 +799,9 @@ augment_substmt : when_stmt { clicon_debug(2,"augment-substmt -> when-s
| description_stmt { clicon_debug(2,"augment-substmt -> description-stmt"); }
| reference_stmt { clicon_debug(2,"augment-substmt -> reference-stmt"); }
| data_def_stmt { clicon_debug(2,"augment-substmt -> data-def-stmt"); }
- | case_stmt { clicon_debug(2,"augment-substmt -> case-stmt");}
+ | case_stmt { clicon_debug(2,"augment-substmt -> case-stmt");}
+ | action_stmt { clicon_debug(2,"augment-substmt -> action-stmt");}
+ | notification_stmt { clicon_debug(2,"augment-substmt -> notification-stmt");}
| { clicon_debug(2,"augment-substmt -> "); }
;
@@ -846,6 +855,17 @@ rpc_substmt : if_feature_stmt { clicon_debug(2,"rpc-substmt -> if-feature-stm
| { clicon_debug(2,"rpc-substmt -> "); }
;
+/* action */
+action_stmt : K_ACTION id_arg_str ';'
+ { if (ysp_add(_yy, Y_ACTION, $2, NULL) == NULL) _YYERROR("39");
+ clicon_debug(2,"action-stmt -> ACTION id-arg-str ;"); }
+ | K_ACTION id_arg_str
+ { if (ysp_add_push(_yy, Y_ACTION, $2) == NULL) _YYERROR("40"); }
+ '{' rpc_substmts '}'
+ { if (ystack_pop(_yy) < 0) _YYERROR("41");
+ clicon_debug(2,"action-stmt -> ACTION id-arg-str { rpc-substmts }"); }
+ ;
+
/* input */
input_stmt : K_INPUT
{ if (ysp_add_push(_yy, Y_INPUT, NULL) == NULL) _YYERROR("42"); }
@@ -875,6 +895,34 @@ output_stmt : K_OUTPUT /* XXX reuse input-substatements since they are same */
;
+/* notification */
+notification_stmt : K_NOTIFICATION id_arg_str ';'
+ { if (ysp_add(_yy, Y_NOTIFICATION, $2, NULL) == NULL) _YYERROR("46");
+ clicon_debug(2,"notification-stmt -> NOTIFICATION id-arg-str ;"); }
+ | K_NOTIFICATION id_arg_str
+ { if (ysp_add_push(_yy, Y_NOTIFICATION, $2) == NULL) _YYERROR("47"); }
+ '{' notification_substmts '}'
+ { if (ystack_pop(_yy) < 0) _YYERROR("48");
+ clicon_debug(2,"notification-stmt -> NOTIFICATION id-arg-str { notification-substmts }"); }
+ ;
+
+notification_substmts : notification_substmts notification_substmt
+ { clicon_debug(2,"notification-substmts -> notification-substmts notification-substmt"); }
+ | notification_substmt
+ { clicon_debug(2,"notification-substmts -> notification-substmt"); }
+ ;
+
+notification_substmt : if_feature_stmt { clicon_debug(2,"notification-substmt -> if-feature-stmt"); }
+ | must_stmt { clicon_debug(2,"notification-substmt -> must-stmt"); }
+ | status_stmt { clicon_debug(2,"notification-substmt -> status-stmt"); }
+ | description_stmt { clicon_debug(2,"notification-substmt -> description-stmt"); }
+ | reference_stmt { clicon_debug(2,"notification-substmt -> reference-stmt"); }
+ | typedef_stmt { clicon_debug(2,"notification-substmt -> typedef-stmt"); }
+ | grouping_stmt { clicon_debug(2,"notification-substmt -> grouping-stmt"); }
+ | data_def_stmt { clicon_debug(2,"notification-substmt -> data-def-stmt"); }
+ | { clicon_debug(2,"notification-substmt -> "); }
+ ;
+
/* Typedef */
typedef_stmt : K_TYPEDEF id_arg_str
{ if (ysp_add_push(_yy, Y_TYPEDEF, $2) == NULL) _YYERROR("46"); }
@@ -964,6 +1012,8 @@ grouping_substmt : status_stmt { clicon_debug(2,"grouping-substmt -> st
| typedef_stmt { clicon_debug(2,"grouping-substmt -> typedef-stmt"); }
| grouping_stmt { clicon_debug(2,"grouping-substmt -> grouping-stmt"); }
| data_def_stmt { clicon_debug(2,"grouping-substmt -> data-def-stmt"); }
+ | action_stmt { clicon_debug(2,"grouping-substmt -> action-stmt"); }
+ | notification_stmt { clicon_debug(2,"grouping-substmt -> notification-stmt"); }
| { clicon_debug(2,"grouping-substmt -> "); }
;
@@ -1310,6 +1360,15 @@ prefix_stmt : K_PREFIX string ';' /* XXX prefix-arg-str */
clicon_debug(2,"prefix-stmt -> PREFIX string ;");}
;
+belongs_to_stmt : K_BELONGS_TO id_arg_str ';'
+
+ { if (ysp_add(_yy, Y_BELONGS_TO, $2, NULL)== NULL) _YYERROR("100");
+ clicon_debug(2,"belongs-to-stmt -> BELONGS-TO id-arg-str ;");}
+ | K_BELONGS_TO id_arg_str '{' prefix_stmt '}'
+ { if (ysp_add(_yy, Y_BELONGS_TO, $2, NULL)== NULL) _YYERROR("98");
+ clicon_debug(2,"belongs-to-stmt -> BELONGS-TO id-arg-str { prefix-stmt } ");}
+ ;
+
description_stmt: K_DESCRIPTION string ';'
{ if (ysp_add(_yy, Y_DESCRIPTION, $2, NULL)== NULL) _YYERROR("101");
clicon_debug(2,"description-stmt -> DESCRIPTION string ;");}
diff --git a/test/README.md b/test/README.md
index 08c92f1a..7ae292bb 100644
--- a/test/README.md
+++ b/test/README.md
@@ -4,8 +4,8 @@ This directory contains testing code for clixon and the example
routing application. Assumes setup of http daemon as describe under apps/restonf
- clixon A top-level script clones clixon in /tmp and starts all.sh. You can copy this file (review it first) and place as cron script
- all.sh Run through all tests named 'test*.sh' in this directory. Therefore, if you place a test in this directory matching 'test*.sh' it will be run automatically.
-- test_auth.sh Auth tests using internal NACM
-- test_auth_ext.sh Auth tests using external NACM
+- test_nacm.sh Auth tests using internal NACM
+- test_nacm_ext.sh Auth tests using external NACM (separate file)
- test_cli.sh CLI tests
- test_netconf.sh Netconf tests
- test_restconf.sh Restconf tests
diff --git a/test/all.sh b/test/all.sh
index 233438ea..d0e92410 100755
--- a/test/all.sh
+++ b/test/all.sh
@@ -1,4 +1,6 @@
#!/bin/bash
+# Run, eg as:
+# ./run.sh 2>&1 | tee test.log
# include err() and new() functions
. ./lib.sh
diff --git a/test/lib.sh b/test/lib.sh
index 35ab1c7a..a518baf3 100755
--- a/test/lib.sh
+++ b/test/lib.sh
@@ -2,13 +2,11 @@
# Define test functions.
# Create working dir as variable "dir"
+#set -e
+
testnr=0
testname=
-# Set to 1 to enable old XSL implementation. Set to nothing, or comment if new.
-# @see include/clixon_custom.h
-#COMPAT_XSL=1
-
# For memcheck
#clixon_cli="valgrind --leak-check=full --show-leak-kinds=all clixon_cli"
clixon_cli=clixon_cli
@@ -19,8 +17,8 @@ clixon_cli=clixon_cli
clixon_netconf=clixon_netconf
# How to run restconf stand-alone and using valgrind
-#sudo su -c "/www-data/clixon_restconf -f $cfg -D" -s /bin/sh www-data
-#sudo su -c "valgrind --leak-check=full --show-leak-kinds=all /www-data/clixon_restconf -f $cfg -D" -s /bin/sh www-data
+#sudo su -c "/www-data/clixon_restconf -f $cfg -D 1" -s /bin/sh www-data
+#sudo su -c "valgrind --trace-children=no --child-silent-after-fork=yes --leak-check=full --show-leak-kinds=all /www-data/clixon_restconf -f $cfg -D 1" -s /bin/sh www-data
#clixon_backend="valgrind --leak-check=full --show-leak-kinds=all clixon_backend"
clixon_backend=clixon_backend
@@ -36,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"
@@ -131,9 +130,10 @@ expecteof(){
ret=$($cmd< /tmp/flag
+ ret=""
+ sleep $wait | cat <(echo $input) -| $cmd | while [ 1 ] ; do
+ read -t 20 r
+# echo "r:$r"
+ ret="$ret$r"
match=$(echo "$ret" | grep -Eo "$expect");
if [ -z "$match" ]; then
- err $expect "$ret"
+ 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_cli.sh b/test/test_cli.sh
index 716fb9d3..5b2574bf 100755
--- a/test/test_cli.sh
+++ b/test/test_cli.sh
@@ -42,7 +42,6 @@ sudo $clixon_backend -s init -f $cfg
if [ $? -ne 0 ]; then
err
fi
-new "cli tests"
new "cli configure top"
expectfn "$clixon_cli -1 -f $cfg set interfaces" 0 "^$"
diff --git a/test/test_feature.sh b/test/test_feature.sh
new file mode 100755
index 00000000..6d444629
--- /dev/null
+++ b/test/test_feature.sh
@@ -0,0 +1,172 @@
+#!/bin/bash
+# Yang features. if-feature. and schema resources according to RFC7895
+APPNAME=example
+# include err() and new() functions and creates $dir
+. ./lib.sh
+
+cfg=$dir/conf_yang.xml
+fyang=$dir/test.yang
+
+cat < $cfg
+
+ $APPNAME:A
+ ietf-routing:router-id
+ $cfg
+ /usr/local/share/$APPNAME/yang
+ $APPNAME
+ /usr/local/lib/$APPNAME/clispec
+ /usr/local/lib/$APPNAME/cli
+ $APPNAME
+ /usr/local/var/$APPNAME/$APPNAME.sock
+ /usr/local/var/$APPNAME/$APPNAME.pidfile
+ 1
+ /usr/local/var/$APPNAME
+ /usr/local/lib/xmldb/text.so
+ true
+
+EOF
+
+cat < $fyang
+module $APPNAME{
+ prefix ex;
+ import ietf-routing {
+ prefix rt;
+ }
+ feature A{
+ description "This test feature is enabled";
+ }
+ feature B{
+ description "This test feature is disabled";
+ }
+ feature C{
+ description "This test feature is disabled";
+ }
+ leaf x{
+ if-feature A;
+ type "string";
+ }
+ leaf y{
+ if-feature B;
+ type "string";
+ }
+ leaf z{
+ type "string";
+ }
+}
+EOF
+new "start backend -s init -f $cfg -y $fyang"
+# kill old backend (if any)
+new "kill old backend"
+sudo clixon_backend -zf $cfg -y $fyang
+if [ $? -ne 0 ]; then
+ err
+fi
+
+new "start backend -s init -f $cfg -y $fyang"
+# start new backend
+sudo $clixon_backend -s init -f $cfg -y $fyang
+if [ $? -ne 0 ]; then
+ err
+fi
+
+new "cli enabled feature"
+expectfn "$clixon_cli -1f $cfg -y $fyang set x foo" 0 ""
+
+new "cli disabled feature"
+expectfn "$clixon_cli -1f $cfg -l o -y $fyang set y foo" 255 "CLI syntax error: \"set y foo\": Unknown command"
+
+new "cli enabled feature in other module"
+expectfn "$clixon_cli -1f $cfg -y $fyang set routing routing-instance A router-id 1.2.3.4" 0 ""
+
+new "cli disabled feature in other module"
+expectfn "$clixon_cli -1f $cfg -l o -y $fyang set routing routing-instance A default-ribs" 255 "CLI syntax error: \"set routing routing-instance A default-ribs\": Unknown command"
+
+new "netconf discard-changes"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$"
+
+new "netconf enabled feature"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" "^]]>]]>$"
+
+new "netconf validate enabled feature"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$"
+
+new "netconf disabled feature"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" '^operation-failedprotocolerrorXML node config/A has no corresponding yang specification (Invalid XML or wrong Yang spec?'
+
+# This test has been broken up into all differetn modules instead of one large
+# reply since the modules change so often
+new "netconf schema resource, RFC 7895"
+ret=$($clixon_netconf -qf $cfg -y $fyang<]]>]]>
+EOF
+)
+new "netconf module A"
+expect="exampleAimplement"
+match=`echo "$ret" | grep -GZo "$expect"`
+if [ -z "$match" ]; then
+ err "$expect" "$ret"
+fi
+
+if false ; then # clixon "config" bug
+new "netconf module clixon-config"
+expect="clixon-config2018-09-30"
+match=`echo "$ret" | grep -GZo "$expect"`
+if [ -z "$match" ]; then
+ err "$expect" "$ret"
+fi
+fi # false
+
+new "netconf module ietf-inet-types"
+expect="ietf-inet-types2013-07-15urn:ietf:params:xml:ns:yang:ietf-inet-typesimplement"
+match=`echo "$ret" | grep -GZo "$expect"`
+if [ -z "$match" ]; then
+ err "$expect" "$ret"
+fi
+
+new "netconf module ietf-interfaces"
+expect="ietf-interfaces2014-05-08urn:ietf:params:xml:ns:yang:ietf-interfacesimplement"
+match=`echo "$ret" | grep -GZo "$expect"`
+if [ -z "$match" ]; then
+ err "$expect" "$ret"
+fi
+
+new "netconf module ietf-netconf"
+expect="module>ietf-netconf2011-06-01urn:ietf:params:xml:ns:netconf:base:1.0implement"
+match=`echo "$ret" | grep -GZo "$expect"`
+if [ -z "$match" ]; then
+ err "$expect" "$ret"
+fi
+
+new "netconf module ietf-routing"
+expect="ietf-routing2014-10-26urn:ietf:params:xml:ns:yang:ietf-routingrouter-idimplement"
+match=`echo "$ret" | grep -GZo "$expect"`
+if [ -z "$match" ]; then
+ err "$expect" "$ret"
+fi
+expect="ietf-yang-library2016-06-21urn:ietf:params:xml:ns:yang:ietf-yang-libraryimplement"
+match=`echo "$ret" | grep -GZo "$expect"`
+if [ -z "$match" ]; then
+ err "$expect" "$ret"
+fi
+
+new "netconf module ietf-yang_types"
+expect="ietf-yang-types2013-07-15urn:ietf:params:xml:ns:yang:ietf-yang-typesimplement"
+match=`echo "$ret" | grep -GZo "$expect"`
+if [ -z "$match" ]; then
+ err "$expect" "$ret"
+fi
+
+new "Kill backend"
+# 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_identity.sh b/test/test_identity.sh
index 684d7416..8d7bea7f 100755
--- a/test/test_identity.sh
+++ b/test/test_identity.sh
@@ -11,7 +11,6 @@ cat < $cfg
$cfg
$dir
- $fyang
/usr/local/lib/$APPNAME/clispec
/usr/local/lib/$APPNAME/backend
example_backend.so$
@@ -163,22 +162,22 @@ new "netconf validate"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorIdentityref validation failed, foo:bar not derived from crypto-alg]]>]]>$"
new "cli set crypto to mc:aes"
-expectfn "$clixon_cli -1 -f $cfg -l o set crypto mc:aes" 0 "^$"
+expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set crypto mc:aes" 0 "^$"
new "cli validate"
-expectfn "$clixon_cli -1 -f $cfg -l o validate" 0 "^$"
+expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o validate" 0 "^$"
new "cli set crypto to aes"
-expectfn "$clixon_cli -1 -f $cfg -l o set crypto aes" 0 "^$"
+expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set crypto aes" 0 "^$"
new "cli validate"
-expectfn "$clixon_cli -1 -f $cfg -l o validate" 0 "^$"
+expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o validate" 0 "^$"
new "cli set crypto to des:des3"
-expectfn "$clixon_cli -1 -f $cfg -l o set crypto des:des3" 0 "^$"
+expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set crypto des:des3" 0 "^$"
new "cli validate"
-expectfn "$clixon_cli -1 -f $cfg -l o validate" 0 "^$"
+expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o validate" 0 "^$"
new "Kill backend"
# Check if still alive
diff --git a/test/test_list.sh b/test/test_list.sh
index 25e2ee37..048a18f3 100755
--- a/test/test_list.sh
+++ b/test/test_list.sh
@@ -5,8 +5,6 @@ APPNAME=example
# include err() and new() functions and creates $dir
. ./lib.sh
-exit 0 # NYI
-
cfg=$dir/conf_yang.xml
fyang=$dir/test.yang
@@ -91,6 +89,8 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace]]>]]>" "^]]>]]>$"
+# NYI
+if false; then
new "minmax: validate should fail"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$"
@@ -117,7 +117,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$"
-
+fi # NYI
# kill backend
diff --git a/test/test_auth.sh b/test/test_nacm.sh
similarity index 97%
rename from test/test_auth.sh
rename to test/test_nacm.sh
index 6837f8e8..764b92c9 100755
--- a/test/test_auth.sh
+++ b/test/test_nacm.sh
@@ -15,7 +15,6 @@ cat < $cfg
$cfg
/usr/local/share/clixon
- $fyang
/usr/local/lib/$APPNAME/clispec
/usr/local/lib/$APPNAME/restconf
/usr/local/lib/$APPNAME/cli
@@ -140,7 +139,7 @@ new "kill old restconf daemon"
sudo pkill -u www-data clixon_restconf
sleep 1
new "start restconf daemon (-a is enable basic authentication)"
-sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang -- -a
+sudo su -c "/www-data/clixon_restconf -f $cfg -y $fyang -- -a" -s /bin/sh www-data &
sleep 1
@@ -148,7 +147,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)" '{"data": null}
+expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" 'null
'
new "auth set authentication config"
diff --git a/test/test_auth_ext.sh b/test/test_nacm_ext.sh
similarity index 94%
rename from test/test_auth_ext.sh
rename to test/test_nacm_ext.sh
index c1806891..3d164dff 100755
--- a/test/test_auth_ext.sh
+++ b/test/test_nacm_ext.sh
@@ -13,11 +13,11 @@ fyang=$dir/test.yang
fyangerr=$dir/err.yang
nacmfile=$dir/nacmfile
+# Note filter out example_backend_nacm.so in CLICON_BACKEND_REGEXP below
cat < $cfg
$cfg
/usr/local/share/example/yang
- $fyang
/usr/local/lib/$APPNAME/clispec
/usr/local/lib/$APPNAME/backend
example_backend.so$
@@ -38,12 +38,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";
@@ -69,21 +63,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
@@ -190,7 +176,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)" '{"data": {"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_netconf.sh b/test/test_netconf.sh
index a520c09a..ddb8cd3b 100755
--- a/test/test_netconf.sh
+++ b/test/test_netconf.sh
@@ -11,8 +11,8 @@ tmp=$dir/tmp.x
cat < $cfg
$cfg
+ 42
/usr/local/share/$APPNAME/yang
- $fyang
/usr/local/lib/$APPNAME/clispec
/usr/local/lib/$APPNAME/backend
example_backend.so$
@@ -46,9 +46,18 @@ module example{
}
rpc empty {
}
+ list server {
+ key name;
+ leaf name {
+ type string;
+ }
+ action reset {
+ }
+ }
identity eth {
base if:interface-type;
}
+
rpc client-rpc {
description "Example local client-side RPC that is processed by the
the netconf/restconf and not sent to the backend.
@@ -65,6 +74,13 @@ module example{
}
}
}
+ container state {
+ config false;
+ description "state data for example application";
+ leaf-list op {
+ type string;
+ }
+ }
}
EOF
@@ -81,8 +97,14 @@ if [ $? -ne 0 ]; then
err
fi
-new "netconf get-config"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^]]>]]>$'
+new "netconf hello"
+expecteof "$clixon_netconf -f $cfg -y $fyang" 0 ']]>]]>' '^urn:ietf:params:netconf:base:1.0urn:ietf:params:netconf:capability:yang-library:1.0?revision=2016-06-21&module-set-id=42urn:ietf:params:netconf:capability:candidate:1:0urn:ietf:params:netconf:capability:validate:1.1urn:ietf:params:netconf:capability:startup:1.0urn:ietf:params:netconf:capability:xpath:1.0urn:ietf:params:netconf:capability:notification:1.0[0-9]*]]>]]>]]>]]>$'
+
+new "netconf get-config double quotes"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^]]>]]>$'
+
+new "netconf get-config single quotes"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$'
new "Add subtree eth/0/0 using none which should not change anything"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "noneeth/0/0]]>]]>" "^]]>]]>$"
@@ -93,16 +115,12 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' "^]]>]]>$"
+
# Too many quotes, (single inside double inside single) need to fool bash
-if [ -z "$COMPAT_XSL" ]; then
cat < $tmp # new
]]>]]>
EOF
-else # old
-cat < $tmp
-]]>]]>
-EOF
-fi
+
new "Check eth/0/0 added using xpath"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" "^eth/0/0ex:ethtrue]]>]]>$"
@@ -122,29 +140,17 @@ new "netconf edit config"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth/0/0eth1true9.2.3.424]]>]]>" "^]]>]]>$"
# Too many quotes
-if [ -z "$COMPAT_XSL" ]; then
cat < $tmp # new
]]>]]>
EOF
-else
-cat < $tmp # old
-]]>]]>
-EOF
-fi
new "netconf get config xpath"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" "^eth1true]]>]]>$"
# Too many quotes
-if [ -z "$COMPAT_XSL" ]; then
cat < $tmp # new
]]>]]>
EOF
-else
-cat < $tmp # old
-]]>]]>
-EOF
-fi
new "netconf get config xpath parent"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" "^eth/0/0trueeth1truetruefalse9.2.3.424]]>]]>$"
@@ -177,10 +183,10 @@ new "netconf edit config merge"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth2ex:ethmerge]]>]]>" "^]]>]]>$"
new "netconf edit ampersand encoding(<&): name:'eth&' type:'t<>'"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth& t< > ]]>]]>" "^]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth&t<>]]>]]>" "^]]>]]>$"
new "netconf get replaced config"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^eth& t< > trueeth1ex:ethtrueeth2ex:ethtrue]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^eth&t<>trueeth1ex:ethtrueeth2ex:ethtrue]]>]]>$"
new "cli show configuration eth& - encoding tests"
expectfn "$clixon_cli -1 -f $cfg -y $fyang show conf cli" 0 "interfaces interface eth& type t<>
@@ -197,10 +203,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 "]]>]]>]]>]]>" "^]]>]]>]]>]]>$"
@@ -241,19 +247,17 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>"
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
-
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_order.sh b/test/test_order.sh
index 848e839a..5d292c1f 100755
--- a/test/test_order.sh
+++ b/test/test_order.sh
@@ -121,8 +121,6 @@ fi
new "verify running from start, should be: l,c,y0,y1,y2,y3; y1 and y3 sorted. Note this fails if XML_SORT set to false"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhoppdbcaabcddbarabarcbarbbarabarbbarcbardbar]]>]]>$"
-if [ -z "$COMPAT_XSL" ]; then #new
-
new "get each ordered-by user leaf-list"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^abar]]>]]>$"
@@ -135,22 +133,6 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^bbar]]>]]>$"
-else # old
-
-new "get each ordered-by user leaf-list"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^abar]]>]]>$"
-
-new "get each ordered-by user leaf-list"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^abar]]>]]>$"
-
-new "get each ordered-by user leaf-list"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^bbar]]>]]>$"
-
-new "get each ordered-by user leaf-list"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^bbar]]>]]>$"
-
-fi
-
new "delete candidate"
expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$"
diff --git a/test/test_perf.sh b/test/test_perf.sh
index 7fe241a0..b5703683 100755
--- a/test/test_perf.sh
+++ b/test/test_perf.sh
@@ -72,7 +72,7 @@ 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 -y $fyang # -D
+sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang # -D 1
sleep 1
diff --git a/test/test_restconf.sh b/test/test_restconf.sh
index 7201eb74..55718814 100755
--- a/test/test_restconf.sh
+++ b/test/test_restconf.sh
@@ -7,12 +7,11 @@ APPNAME=example
cfg=$dir/conf.xml
fyang=$dir/restconf.yang
-# example
cat < $cfg
$cfg
/usr/local/share/$APPNAME/yang
- $fyang
+ example
/usr/local/lib/$APPNAME/clispec
/usr/local/lib/$APPNAME/backend
example_backend.so$
@@ -25,6 +24,7 @@ cat < $cfg
1
/usr/local/var/$APPNAME
/usr/local/lib/xmldb/text.so
+ true
EOF
@@ -70,11 +70,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"
@@ -92,7 +99,7 @@ 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 -y $fyang -D
+sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang # -D 1
sleep 1
@@ -135,6 +142,10 @@ if [ -z "$match" ]; then
err "$expect" "$ret"
fi
+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","conformance-type": "implement"}]}
+
'
+
new "restconf options. RFC 8040 4.1"
expectfn "curl -i -s -X OPTIONS http://localhost/restconf/data" 0 "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE"
@@ -142,49 +153,56 @@ new "restconf head. RFC 8040 4.2"
expectfn "curl -s -I http://localhost/restconf/data" 0 "HTTP/1.1 200 OK"
#Content-Type: application/yang-data+json"
-new2 "restconf empty rpc"
+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)" '{"data": {"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 + 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: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)
-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)" '{"data": {"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
@@ -198,22 +216,26 @@ 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"}}
'
-new2 "restconf delete interfaces"
+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" 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 ""
# XXX cant get this to work
#expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type\":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces)" ""
-new2 "restconf Check eth/0/0 added"
-expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}}
+new2 "restconf Check eth/0/0 added config"
+expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]}}
+
'
+
+new2 "restconf Check eth/0/0 added state"
+expecteq "$(curl -s -G http://localhost/restconf/data/state)" '{"state": {"op": "42"}}
'
new2 "restconf Re-post eth/0/0 which should generate error"
@@ -226,7 +248,7 @@ new "Add nothing using POST"
expectfn 'curl -s -X POST http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "malformed-message","error-type": "rpc","error-severity": "error","error-message": " on line 1: syntax error at or before:'
new2 "restconf Check description added"
-expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "ex:eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}}
+expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "ex:eth","enabled": true}]}}
'
new "restconf delete eth/0/0"
@@ -242,7 +264,7 @@ new "restconf Add subtree eth/0/0 using PUT"
expecteq "$(curl -s -X PUT -d '{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" ""
new2 "restconf get subtree"
-expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}}
+expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]}}
'
new2 "restconf rpc using POST json"
diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh
index dfd7a265..5f796d35 100755
--- a/test/test_restconf2.sh
+++ b/test/test_restconf2.sh
@@ -12,7 +12,6 @@ cat < $cfg
$cfg
/usr/local/var
- $fyang
false
/usr/local/var/$APPNAME/$APPNAME.sock
$dir/restconf.pidfile
@@ -60,7 +59,7 @@ 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
+sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang # -D 1
sleep 1
@@ -69,8 +68,8 @@ new "restconf tests"
new "restconf POST initial tree"
expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 ""
-new "restconf GET datastore"
-expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": {"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}}'
+new "restconf GET datastore intial"
+expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 '{"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}'
new "restconf GET interface"
expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0" 0 '{"interface": \[{"name": "local0","type": "regular"}\]}'
@@ -94,31 +93,31 @@ new "restconf DELETE"
expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' 0 ""
new "restconf GET null datastore"
-expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": null}'
+expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 'null'
new "restconf POST initial tree"
expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 ""
new "restconf GET initial tree"
-expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": {"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}}'
+expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 '{"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}'
new "restconf DELETE whole datastore"
expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 ""
new "restconf GET null datastore"
-expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": null}'
+expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 'null'
new "restconf PUT initial datastore"
expectfn 'curl -s -X PUT -d {"data":{"cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 ""
new "restconf GET datastore"
-expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": {"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}}'
+expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 '{"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}'
new "restconf PUT replace datastore"
expectfn 'curl -s -X PUT -d {"data":{"cont2":{"name":"foo"}}} http://localhost/restconf/data' 0 ""
new "restconf GET replaced datastore"
-expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": {"cont2": {"name": "foo"}}}'
+expectfn "curl -s -X GET http://localhost/restconf/data/cont2" 0 '{"cont2": {"name": "foo"}}'
new "restconf PUT initial datastore again"
expectfn 'curl -s -X PUT -d {"data":{"cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 ""
@@ -126,9 +125,8 @@ expectfn 'curl -s -X PUT -d {"data":{"cont1":{"interface":{"name":"local0","type
new "restconf PUT change interface"
expectfn 'curl -s -X PUT -d {"interface":{"name":"local0","type":"atm0"}} http://localhost/restconf/data/cont1/interface=local0' 0 ""
-
new "restconf GET datastore atm"
-expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": {"cont1": {"interface": \[{"name": "local0","type": "atm0"}\]}}}'
+expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 '{"cont1": {"interface": \[{"name": "local0","type": "atm0"}\]}}'
new "restconf PUT add interface"
expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' 0 ""
diff --git a/test/test_stream.sh b/test/test_stream.sh
new file mode 100755
index 00000000..66f5414d
--- /dev/null
+++ b/test/test_stream.sh
@@ -0,0 +1,293 @@
+#!/bin/bash
+# Tests for event streams using notifications
+# Assumptions:
+# 1. http server setup, such as nginx described in apps/restconf/README.md
+# especially SSE - ngchan setup
+# 2. Example stream as Clixon example which needs registration, callback and
+# notification generating code every 5s
+#
+# Testing of streams is quite complicated.
+# Here are some testing dimensions in restconf alone:
+# - start/stop subscription
+# - start-time/stop-time in subscription
+# - stream retention time
+# - native vs nchan implementation
+# Focussing on 1-3
+# 2a) start sub 8s - expect 2 notifications
+# 2b) start sub 8s - stoptime after 5s - expect 1 notifications
+# 2c) start sub 8s - replay from start -8s - expect 4 notifications
+# 2d) start sub 8s - replay from start -8s to stop +4s - expect 3 notifications
+# 2e) start sub 8s - replay from -90s w retention 60s - expect 10 notifications
+# Note the sleeps are mainly for valgrind usage
+
+APPNAME=example
+UTIL=../util/clixon_util_stream
+if [ ! -x $UTIL ]; then
+ echo "$UTIL not found. To build: (cd ../util; make clixon_util_stream)"
+ exit 1
+fi
+DATE=$(date +"%Y-%m-%d")
+# include err() and new() functions and creates $dir
+. ./lib.sh
+cfg=$dir/conf.xml
+fyang=$dir/stream.yang
+xml=$dir/xml.xml
+
+# example
+cat < $cfg
+
+ $cfg
+ /usr/local/share/clixon
+ 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
+ /usr/local/lib/$APPNAME/clispec
+ /usr/local/lib/$APPNAME/cli
+ $APPNAME
+ true
+ true
+ true
+ streams
+ https://localhost
+ 60
+
+EOF
+
+# For nchan testing add this line to above config
+# http://localhost/pub
+
+# 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.";
+ revision "2016-07-07" {
+ 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 reportingEntity {
+ description "Event specific information.";
+ leaf card {
+ type string;
+ description "Line card identifier.";
+ }
+ }
+ leaf severity {
+ type string;
+ description "Event severity description.";
+ }
+ }
+ container state {
+ config false;
+ description "state data for the example application (must be here for example get operation)";
+ leaf-list op {
+ type string;
+ }
+ }
+ }
+EOF
+
+# kill old backend (if any)
+new "kill old backend"
+sudo clixon_backend -zf $cfg
+if [ $? -ne 0 ]; then
+ err
+fi
+new "start backend -s init -f $cfg -y $fyang"
+sudo $clixon_backend -s init -f $cfg -y $fyang # -D 1
+
+if [ $? -ne 0 ]; then
+ err
+fi
+
+new "kill old restconf daemon"
+sudo pkill -u www-data clixon_restconf
+
+new "start restconf daemon"
+
+sudo su -c "/www-data/clixon_restconf -f $cfg -y $fyang" -s /bin/sh www-data &
+
+sleep 2
+
+#
+# 1. Netconf RFC5277 stream testing
+new "1. Netconf RFC5277 stream testing"
+# 1.1 Stream discovery
+new "netconf event stream discovery RFC5277 Sec 3.2.5"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'EXAMPLEExample event streamtrue]]>]]>'
+
+new "netconf event stream discovery RFC8040 Sec 6.2"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'EXAMPLEExample event streamtruexmlhttps://localhost/streams/EXAMPLE]]>]]>'
+
+#
+# 1.2 Netconf stream subscription
+new "netconf EXAMPLE subscription"
+expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLE]]>]]>' '^]]>]]>20' 10
+
+new "netconf subscription with empty startTime"
+expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLE]]>]]>' '^]]>]]>20' 5
+
+new "netconf EXAMPLE subscription with simple filter"
+expectwait "$clixon_netconf -qf $cfg -y $fyang" "EXAMPLE]]>]]>" '^]]>]]>20' 5
+
+new "netconf EXAMPLE subscription with filter classifier"
+expectwait "$clixon_netconf -qf $cfg -y $fyang" "EXAMPLE]]>]]>" '^]]>]]>20' 5
+
+new "netconf NONEXIST subscription"
+expectwait "$clixon_netconf -qf $cfg -y $fyang" 'NONEXIST]]>]]>' '^invalid-valueapplicationerrorNo such stream]]>]]>$' 5
+
+new "netconf EXAMPLE subscription with wrong date"
+expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLEkallekaka]]>]]>' '^bad-elementapplicationstartTimeerrorExpected timestamp]]>]]>$' 0
+
+#new "netconf EXAMPLE subscription with replay"
+#NOW=$(date +"%Y-%m-%dT%H:%M:%S")
+#sleep 10
+#expectwait "$clixon_netconf -qf $cfg -y $fyang" "EXAMPLE$NOW]]>]]>" '^]]>]]>20' 10
+sleep 2
+#
+# 2. Restconf RFC8040 stream testing
+new "2. Restconf RFC8040 stream testing"
+# 2.1 Stream discovery
+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": "EXAMPLE","description": "Example event stream","replay-support": true,"access": \[{"encoding": "xml","location": "https://localhost/streams/EXAMPLE"}\]}\]}'
+
+sleep 2
+new "restconf subscribe RFC8040 Sec 6.3, get location"
+expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams/stream=EXAMPLE/access=xml/location" 0 '{"location": "https://localhost/streams/EXAMPLE"}'
+
+sleep 2
+# Restconf stream subscription RFC8040 Sec 6.3
+# Start Subscription w error
+new "restconf monitor event nonexist stream"
+expectwait 'curl -s -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" http://localhost/streams/NOTEXIST' 0 'invalid-valueapplicationerrorNo such stream' 2
+
+# 2a) start subscription 8s - expect 1-2 notifications
+new "2a) start subscriptions 8s - expect 1-2 notifications"
+ret=$($UTIL -u http://localhost/streams/EXAMPLE -t 8)
+expect="data: ${DATE}T[0-9:.]*faultEthernet0major"
+
+match=$(echo "$ret" | grep -Eo "$expect")
+if [ -z "$match" ]; then
+ err "$expect" "$ret"
+fi
+nr=$(echo "$ret" | grep -c "data:")
+if [ $nr -lt 1 -o $nr -gt 2 ]; then
+ err 2 "$nr"
+fi
+
+sleep 2
+# 2b) start subscription 8s - stoptime after 5s - expect 1-2 notifications
+new "2b) start subscriptions 8s - stoptime after 5s - expect 1-2 notifications"
+ret=$($UTIL -u http://localhost/streams/EXAMPLE -t 8 -e +10)
+expect="data: ${DATE}T[0-9:.]*faultEthernet0major"
+match=$(echo "$ret" | grep -Eo "$expect")
+if [ -z "$match" ]; then
+ err "$expect" "$ret"
+fi
+nr=$(echo "$ret" | grep -c "data:")
+if [ $nr -lt 1 -o $nr -gt 2 ]; then
+ err 1 "$nr"
+fi
+
+# 2c
+new "2c) start sub 8s - replay from start -8s - expect 3-4 notifications"
+ret=$($UTIL -u http://localhost/streams/EXAMPLE -t 10 -s -8)
+expect="data: ${DATE}T[0-9:.]*faultEthernet0major"
+match=$(echo "$ret" | grep -Eo "$expect")
+if [ -z "$match" ]; then
+ err "$expect" "$ret"
+fi
+nr=$(echo "$ret" | grep -c "data:")
+if [ $nr -lt 3 -o $nr -gt 4 ]; then
+ err 4 "$nr"
+fi
+
+# 2d) start sub 8s - replay from start -8s to stop +4s - expect 3 notifications
+new "2d) start sub 8s - replay from start -8s to stop +4s - expect 3 notifications"
+ret=$($UTIL -u http://localhost/streams/EXAMPLE -t 10 -s -30 -e +4)
+expect="data: ${DATE}T[0-9:.]*faultEthernet0major"
+match=$(echo "$ret" | grep -Eo "$expect")
+if [ -z "$match" ]; then
+ err "$expect" "$ret"
+fi
+nr=$(echo "$ret" | grep -c "data:")
+if [ $nr -lt 4 -o $nr -gt 10 ]; then
+ err 6 "$nr"
+fi
+
+# 2e) start sub 8s - replay from -90s w retention 60s - expect 10 notifications
+new "2e) start sub 8s - replay from -90s w retention 60s - expect 10 notifications"
+ret=$($UTIL -u http://localhost/streams/EXAMPLE -t 10 -s -90 -e +0)
+expect="data: ${DATE}T[0-9:.]*faultEthernet0major"
+match=$(echo "$ret" | grep -Eo "$expect")
+if [ -z "$match" ]; then
+ err "$expect" "$ret"
+fi
+nr=$(echo "$ret" | grep -c "data:")
+
+if [ $nr -lt 10 -o $nr -gt 14 ]; then
+ err 10 "$nr"
+fi
+
+# Try parallell
+# start background job
+curl -s -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" "http://localhost/streams/EXAMPLE" > /dev/null &
+
+new "Start subscription in parallell"
+ret=$($UTIL -u http://localhost/streams/EXAMPLE -t 8)
+expect="data: ${DATE}T[0-9:.]*faultEthernet0major"
+
+match=$(echo "$ret" | grep -Eo "$expect")
+if [ -z "$match" ]; then
+ err "$expect" "$ret"
+fi
+nr=$(echo "$ret" | grep -c "data:")
+if [ $nr -lt 1 -o $nr -gt 2 ]; then
+ err 2 "$nr"
+fi
+
+#--------------------------------------------------------------------
+# NCHAN Need manual testing
+echo "Nchan streams requires manual testing"
+echo "Add http://localhost/pub to config"
+echo "Eg: curl -H \"Accept: text/event-stream\" -s -X GET http://localhost/sub/EXAMPLE"
+
+#-----------------
+
+sudo pkill -u www-data clixon_restconf
+
+new "Kill backend"
+# 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_xml.sh b/test/test_xml.sh
index 9550c402..b2937201 100755
--- a/test/test_xml.sh
+++ b/test/test_xml.sh
@@ -37,11 +37,20 @@ new "xml encode <>&"
expecteof "$PROG" 0 "$XML" "^$XML$"
XML=$(cat <To allow attribute values to contain both single and double quotes, the apostrophe or single-quote character ' may be represented as ' and the double-quote character as "
+To allow attribute values to contain both single and double quotes, the apostrophe or single-quote character ' may be represented as ' and the double-quote character as "
EOF
)
new "xml optional encode single and double quote"
expecteof "$PROG" 0 "$XML" "^To allow attribute values to contain both single and double quotes, the apostrophe or single-quote character ' may be represented as ' and the double-quote character as \"$"
+new "Double quotes for attributes"
+expecteof "$PROG" 0 '' '^$'
+
+new "Single quotes for attributes (returns double quotes but at least parses right)"
+expecteof "$PROG" 0 "" '^$'
+
+new "Mixed quotes"
+expecteof "$PROG" 0 "" '^$'
+
rm -rf $dir
diff --git a/test/test_yang.sh b/test/test_yang.sh
index 7ebbbf12..f54e03bc 100755
--- a/test/test_yang.sh
+++ b/test/test_yang.sh
@@ -21,6 +21,7 @@ cat < $cfg
1
/usr/local/var/$APPNAME
/usr/local/lib/xmldb/text.so
+ true
EOF
@@ -113,10 +114,25 @@ fi
new "cli defined extension"
expectfn "$clixon_cli -1f $cfg -y $fyang show version" 0 "3."
+new "empty values in leaf-list"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "a]]>]]>" "^]]>]]>$"
+
+new "empty values in leaf-list2"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$"
+
+new "netconf get config"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^a]]>]]>$"
+
+new "netconf discard-changes"
+expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$"
+
new "cli not defined extension"
# This text yields an error, but the test cannot detect the error message yet
#expectfn "$clixon_cli -1f $cfg -y $fyangerr show version" 0 "Yang error: Extension ex:not-defined not found"
+new "netconf schema resource, RFC 7895"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'ietf-yang-types2013-07-15urn:ietf:params:xml:ns:yang:ietf-yang-typesimplement'
+
new "netconf edit config"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "125one]]>]]>" "^]]>]]>$"
@@ -136,16 +152,11 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhopp]]>]]>$"
-if [ -z "$COMPAT_XSL" ]; then # new
new "netconf get leaf-list path"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhopp]]>]]>$"
-else # old
-new "netconf get leaf-list path"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhopp]]>]]>$"
-fi
new "netconf get (should be some)"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^125one]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^125one"
new "cli set leaf-list"
expectfn "$clixon_cli -1f $cfg -y $fyang set x f e foo" 0 ""
diff --git a/test/test_yang_parse.sh b/test/test_yang_parse.sh
index 5c3736eb..9cc52da2 100755
--- a/test/test_yang_parse.sh
+++ b/test/test_yang_parse.sh
@@ -1,7 +1,9 @@
#!/bin/bash
-# Test: XML parser tests
+# Test: YANG parser tests
+# First an example yang, second all openconfig yangs
#PROG="valgrind --leak-check=full --show-leak-kinds=all ../util/clixon_util_yang"
PROG=../util/clixon_util_yang
+OPENCONFIG=~/syssrc/openconfig
# include err() and new() functions and creates $dir
. ./lib.sh
@@ -13,13 +15,28 @@ module test{
description "Example from RFC 6020";
argument "name";
}
- ex:not-defined ARGUMENT;;
+ ex:not-defined ARGUMENT;
}
EOF
)
new "yang parse"
-expecteof "$PROG" 0 "$YANG" "^$YANG$"
+#expecteof "$PROG" 0 "$YANG" "^$YANG$"
+if [ ! -d $OPENCONFIG ]; then
+ echo "$OPENCONFIG not found. Do git clone https://github.com/openconfig/public and point DIR to it to run these tests"
+ rm -rf $dir
+ exit 0
+fi
+
+# Openconfig
+new "Openconfig"
+files=$(find $OPENCONFIG -name "*.yang")
+for f in $files; do
+ new "$f"
+ YANG=$(cat $f)
+ # expecteof "$PROG" 0 "$YANG" "module"
+done
rm -rf $dir
+
diff --git a/util/Makefile.in b/util/Makefile.in
index b6581003..4814e91a 100644
--- a/util/Makefile.in
+++ b/util/Makefile.in
@@ -57,7 +57,7 @@ INSTALL = @INSTALL@
INSTALL_LIB = @INSTALL@
INSTALLFLAGS = @INSTALLFLAGS@
LDFLAGS = @LDFLAGS@
-LIBS = @LIBS@
+LIBS = @LIBS@
CPPFLAGS = @CPPFLAGS@
@@ -70,10 +70,12 @@ APPSRC = clixon_util_xml.c
APPSRC += clixon_util_json.c
APPSRC += clixon_util_yang.c
APPSRC += clixon_util_xpath.c
+# APPSRC += clixon_util_stream.c # Needs curl
APPS = $(APPSRC:.c=)
all: $(APPS)
+ @echo "You may want to make clixon_util_stream separately (curl dependency)"
clean:
rm -f $(APPS) *.core
@@ -91,6 +93,9 @@ clixon_util_yang: clixon_util_yang.c $(MYLIB)
clixon_util_xpath: clixon_util_xpath.c $(MYLIB)
$(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@
+clixon_util_stream: clixon_util_stream.c $(MYLIB)
+ $(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -lcurl -o $@
+
distclean: clean
rm -f Makefile *~ .depend
diff --git a/util/clixon_util_stream.c b/util/clixon_util_stream.c
new file mode 100644
index 00000000..10275ecf
--- /dev/null
+++ b/util/clixon_util_stream.c
@@ -0,0 +1,286 @@
+/*
+ *
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLIXON.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ Alternatively, the contents of this file may be used under the terms of
+ the GNU General Public License Version 3 or later (the "GPL"),
+ in which case the provisions of the GPL are applicable instead
+ of those above. If you wish to allow use of your version of this file only
+ under the terms of the GPL, and not to allow others to
+ use your version of this file under the terms of Apache License version 2,
+ indicate your decision by deleting the provisions above and replace them with
+ the notice and other provisions required by the GPL. If you do not delete
+ the provisions above, a recipient may use your version of this file under
+ the terms of any one of the Apache License version 2 or the GPL.
+
+ ***** END LICENSE BLOCK *****
+
+ * Stream restconf support functions.
+ * (Original in grideye)
+ * Example: clixon_util_stream -u http://localhost/streams/EXAMPLE -s 2018-10-21T19:22:16
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "clixon_config.h" /* generated by config & autoconf */
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* cligen */
+#include
+
+/* clixon */
+#include "clixon/clixon.h"
+
+/*
+ * Types (curl)
+ */
+struct curlbuf{
+ size_t b_len;
+ char *b_buf;
+};
+
+/*
+ * For the asynchronous case. I think we must handle the case where of many of these
+ * come in before we can handle them in the upper-level polling routine.
+ * realloc. Therefore, we append new data to the userdata buffer.
+ */
+static size_t
+curl_get_cb(void *ptr,
+ size_t size,
+ size_t nmemb,
+ void *userdata)
+{
+ struct curlbuf *buf = (struct curlbuf *)userdata;
+ int len;
+
+ len = size*nmemb;
+ if ((buf->b_buf = realloc(buf->b_buf, buf->b_len+len+1)) == NULL)
+ return 0;
+ memcpy(buf->b_buf+buf->b_len, ptr, len);
+ buf->b_len += len;
+ buf->b_buf[buf->b_len] = '\0';
+ // fprintf(stderr, "%s\n", buf->b_buf);
+ return len;
+}
+
+/*! Given an URL and data to post, do a (curl) get request with data.
+ *
+ * If getdata is set, return the (malloced) data (which should be freed).
+ *
+ * @param[in] start 'start-time' parameter that will be URL-encoded
+ * @retval -1 fatal error
+ * @retval 1 ok
+ *
+ * @note curl_easy_perform blocks
+ * @note New handle is created every time, the handle can be re-used for
+ * better TCP performance
+ */
+int
+stream_url_get(char *url,
+ char *start,
+ char *stop,
+ int timeout,
+ char **getdata)
+{
+ int retval = -1;
+ CURL *curl;
+ char *err = NULL;
+ char *encoded = NULL;
+ struct curlbuf cb = {0, };
+ cbuf *cbf = NULL;
+ struct curl_slist *list = NULL;
+ int ret;
+
+ clicon_debug(1, "%s: curl -G %s start-time=%s stop-time=%s",
+ __FUNCTION__, url, start?start:"", stop?stop:"");
+ /* Set up curl for doing the communication with the controller */
+ if ((curl = curl_easy_init()) == NULL) {
+ clicon_err(OE_PLUGIN, errno, "curl_easy_init");
+ goto done;
+ }
+ if ((cbf = cbuf_new()) == NULL)
+ goto done;
+
+ if ((err = malloc(CURL_ERROR_SIZE)) == NULL) {
+ clicon_debug(1, "%s: malloc", __FUNCTION__);
+ goto done;
+ }
+ curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
+ /* HEADERS */
+ list = curl_slist_append(list, "Accept: text/event-stream");
+ // list = curl_slist_append(list, "Cache-Control: no-cache");
+ // list = curl_slist_append(list, "Connection: keep-alive");
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
+ /* specify URL to get */
+ cprintf(cbf, "%s", url);
+ if (strlen(start)||strlen(stop))
+ cprintf(cbf, "?");
+ if (strlen(start)){
+ if ((encoded = curl_easy_escape(curl, start, 0)) == NULL)
+ goto done;
+ cprintf(cbf, "start-time=%s", encoded);
+ curl_free(encoded);
+ encoded = NULL;
+ }
+ if (strlen(stop)){
+ if (strlen(start))
+ cprintf(cbf, "&");
+ if ((encoded = curl_easy_escape(curl, stop, 0)) == NULL)
+ goto done;
+ cprintf(cbf, "stop-time=%s", encoded);
+ curl_free(encoded);
+ encoded = NULL;
+ }
+ clicon_debug(1, "url: %s\n", cbuf_get(cbf));
+ curl_easy_setopt(curl, CURLOPT_URL, cbuf_get(cbf));
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_get_cb);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &cb);
+
+ /* some servers don't like requests that are made without a user-agent
+ field, so we provide one */
+
+ curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, err);
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
+ curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
+ ret = curl_easy_perform(curl);
+ if (ret != CURLE_OPERATION_TIMEDOUT && ret != CURLE_OK){
+ fprintf(stderr, "curl: %s %d", err, ret);
+ goto done;
+ }
+ if (getdata && cb.b_buf){
+ *getdata = cb.b_buf;
+ cb.b_buf = NULL;
+ }
+ retval = 1;
+ done:
+ if (cbf)
+ cbuf_free(cbf);
+ if (err)
+ free(err);
+ if (encoded)
+ curl_free(encoded);
+ if (cb.b_buf)
+ free(cb.b_buf);
+ if (curl)
+ curl_easy_cleanup(curl); /* cleanup */
+ return retval;
+}
+
+static int
+usage(char *argv0)
+{
+ fprintf(stderr, "usage:%s *\n"
+ "where options are:\n"
+ "\t-h\t\tHelp\n"
+ "\t-D \tDebug level\n"
+ "\t-u \tURL (mandatory)\n"
+ "\t-s \tStart-time (format: 2018-10-21T19:22:16 OR +/-s\n"
+ "\t-e \tStop-time (same format as start)\n"
+ "\t-t \tTimeout (default: 10)\n"
+ , argv0);
+
+ exit(0);
+}
+
+int
+main(int argc, char **argv)
+{
+ cbuf *cb = cbuf_new();
+ char *url = NULL;
+ char *getdata = NULL;
+ int timeout = 10;
+ char start[27] = {0,}; /* strlen = 0 */
+ char stop[27] = {0,};
+ char c;
+ char *argv0 = argv[0];
+ struct timeval now;
+
+ clicon_log_init("xpath", LOG_DEBUG, CLICON_LOG_STDERR);
+ gettimeofday(&now, NULL);
+ optind = 1;
+ opterr = 0;
+ while ((c = getopt(argc, argv, "hDu:s:e:t:")) != -1)
+ switch (c) {
+ case 'h':
+ usage(argv0);
+ break;
+ case 'D':
+ debug++;
+ break;
+ case 'u': /* URL */
+ url = optarg;
+ break;
+ case 's': /* start-time */
+ if (*optarg == '+' || *optarg == '-'){
+ struct timeval t;
+ t = now;
+ t.tv_sec += atoi(optarg);
+ if (time2str(t, start, sizeof(start)) < 0)
+ goto done;
+ }
+ else
+ strcpy(start, optarg);
+ break;
+ case 'e': /* stop-time */
+ if (*optarg == '+' || *optarg == '-'){
+ struct timeval t;
+ t = now;
+ t.tv_sec += atoi(optarg);
+ if (time2str(t, stop, sizeof(stop)) < 0)
+ goto done;
+ }
+ else
+ strcpy(stop, optarg);
+ break;
+ case 't': /* timeout */
+ timeout = atoi(optarg);
+ break;
+ default:
+ usage(argv[0]);
+ break;
+ }
+ if (url == NULL)
+ usage(argv[0]);
+ curl_global_init(0);
+ if (stream_url_get(url, start, stop, timeout, &getdata) < 0)
+ goto done;
+ if (getdata)
+ fprintf(stdout, "%s", getdata);
+ fflush(stdout);
+ done:
+ curl_global_cleanup();
+ if (getdata)
+ free(getdata);
+ if (cb)
+ cbuf_free(cb);
+ return 0;
+}
+
+
diff --git a/util/clixon_util_yang.c b/util/clixon_util_yang.c
index 22847b0d..9ebe991f 100644
--- a/util/clixon_util_yang.c
+++ b/util/clixon_util_yang.c
@@ -78,10 +78,11 @@ main(int argc, char **argv)
usage(argv[0]);
return -1;
}
+ clicon_log_init("clixon_util_yang", LOG_INFO, CLICON_LOG_STDERR);
if ((yspec = yspec_new()) == NULL)
goto done;
- if (yang_parse_file(0, "yang test", yspec) < 0){
- fprintf(stderr, "xml parse error %s\n", clicon_err_reason);
+ if (yang_parse_file(0, "yang test", yspec) == NULL){
+ fprintf(stderr, "yang parse error %s\n", clicon_err_reason);
return -1;
}
yang_print(stdout, (yang_node*)yspec);
diff --git a/yang/Makefile.in b/yang/Makefile.in
index 469f026b..b595b5ea 100644
--- a/yang/Makefile.in
+++ b/yang/Makefile.in
@@ -40,11 +40,14 @@ datarootdir = @datarootdir@
CLIXON_DATADIR = @CLIXON_DATADIR@
-YANGSPECS = clixon-config@2018-04-30.yang
+YANGSPECS = clixon-config@2018-10-21.yang
YANGSPECS += ietf-netconf@2011-06-01.yang
YANGSPECS += ietf-netconf-acm@2018-02-14.yang
YANGSPECS += ietf-inet-types@2013-07-15.yang
YANGSPECS += ietf-yang-types@2013-07-15.yang
+YANGSPECS += ietf-restconf-monitoring@2017-01-26.yang
+YANGSPECS += ietf-netconf-notification@2008-07-01.yang
+YANGSPECS += ietf-yang-library@2016-06-21.yang
APPNAME = clixon # subdir ehere these files are installed
diff --git a/yang/clixon-config@2018-04-30.yang b/yang/clixon-config@2018-04-30.yang
index c629a3c7..d395e543 100644
--- a/yang/clixon-config@2018-04-30.yang
+++ b/yang/clixon-config@2018-04-30.yang
@@ -38,9 +38,9 @@ module clixon-config {
***** END LICENSE BLOCK *****";
- revision 2018-02-12 {
+ revision 2018-04-30 {
description
- "Added pretty print for datastore";
+ "Released with Clixon 3.6";
}
typedef startup_mode{
description
diff --git a/yang/clixon-config@2018-10-21.yang b/yang/clixon-config@2018-10-21.yang
new file mode 100644
index 00000000..fb07efa1
--- /dev/null
+++ b/yang/clixon-config@2018-10-21.yang
@@ -0,0 +1,430 @@
+module clixon-config {
+
+ prefix cc;
+
+ organization
+ "Clicon / Clixon";
+
+ contact
+ "Olof Hagsand ";
+
+ description
+ "Clixon configuration file
+ ***** BEGIN LICENSE BLOCK *****
+ Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLIXON
+
+ Licensed under the Apache License, Version 2.0 (the \"License\");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an \"AS IS\" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ Alternatively, the contents of this file may be used under the terms of
+ the GNU General Public License Version 3 or later (the \"GPL\"),
+ in which case the provisions of the GPL are applicable instead
+ of those above. If you wish to allow use of your version of this file only
+ under the terms of the GPL, and not to allow others to
+ use your version of this file under the terms of Apache License version 2,
+ indicate your decision by deleting the provisions above and replace them with
+ the notice and other provisions required by the GPL. If you do not delete
+ the provisions above, a recipient may use your version of this file under
+ the terms of any one of the Apache License version 2 or the GPL.
+
+ ***** END LICENSE BLOCK *****";
+
+ revision 2018-10-21 {
+ description
+ "Released in Clixon 3.8";
+ }
+ typedef startup_mode{
+ description
+ "Which method to boot/start clicon backend.
+ The methods differ in how they reach a running state
+ Which source database to commit from, if any.";
+ type enumeration{
+ enum none{
+ description
+ "Do not touch running state
+ Typically after crash when running state and db are synched";
+ }
+ enum init{
+ description
+ "Initialize running state.
+ Start with a completely clean running state";
+ }
+ enum running{
+ description
+ "Commit running db configuration into running state
+ After reboot if a persistent running db exists";
+ }
+ enum startup{
+ description
+ "Commit startup configuration into running state
+ After reboot when no persistent running db exists";
+ }
+ }
+ }
+ typedef xmldb_format{
+ description
+ "Format of TEXT xml database format.";
+ type enumeration{
+ enum xml{
+ description "Save and load xmldb as XML";
+ }
+ enum json{
+ description "Save and load xmldb as JSON";
+ }
+ }
+ }
+ typedef cli_genmodel_type{
+ description
+ "How to generate CLI from YANG model,
+ eg list a{ key x; leaf x; leaf y;}";
+ type enumeration{
+ enum NONE{
+ description "No extra keywords: a ";
+ }
+ enum VARS{
+ description "Keywords on non-key variables: a y ";
+ }
+ enum ALL{
+ description "Keywords on all variables: a x y ";
+ }
+ }
+ }
+ typedef nacm_mode{
+ description
+ "Mode of RFC8341 Network Configuration Access Control Model.
+ It is unclear from the RFC whether NACM rules are internal
+ in a configuration (ie embedded in regular config) or external/OOB
+ in s separate, specific NACM-config";
+ type enumeration{
+ enum disabled{
+ description "NACM is disabled";
+ }
+ enum internal{
+ description "NACM is enabled and available in the regular config";
+ }
+ enum external{
+ description "NACM is enabled and available in a separate config";
+ }
+ }
+ }
+ container config {
+ leaf-list CLICON_FEATURE {
+ description
+ "Supported features as used by YANG feature/if-feature
+ value is: :, where and
+ are either names, or the special character '*'.
+ *:an* means enable all features
+ :* means enable all features in the specified module
+ *: means enable the specific feature in all modules";
+ type string;
+ }
+ leaf CLICON_CONFIGFILE{
+ type string;
+ description
+ "Location of configuration-file for default values (this file)";
+ }
+ leaf CLICON_YANG_DIR {
+ type string;
+ mandatory true;
+ description
+ "Location of YANG module and submodule files.";
+ }
+ leaf CLICON_YANG_MODULE_MAIN {
+ type string;
+ default "clicon";
+ description
+ "Option used to construct initial yang file:
+ [@]";
+ }
+ leaf CLICON_YANG_MODULE_REVISION {
+ type string;
+ description
+ "Option used to construct initial yang file:
+ [@]";
+ }
+ leaf CLICON_BACKEND_DIR {
+ type string;
+ description
+ "Location of backend .so plugins. Load all .so
+ plugins in this dir as backend plugins";
+ }
+ leaf CLICON_BACKEND_REGEXP {
+ type string;
+ description
+ "Regexp of matching backend plugins in CLICON_BACKEND_DIR";
+ default "(.so)$";
+ }
+ leaf CLICON_NETCONF_DIR {
+ type string;
+ description "Location of netconf (frontend) .so plugins";
+ }
+ leaf CLICON_RESTCONF_DIR {
+ type string;
+ description
+ "Location of restconf (frontend) .so plugins. Load all .so
+ plugins in this dir as restconf code plugins";
+ }
+ leaf CLICON_RESTCONF_PATH {
+ type string;
+ default "/www-data/fastcgi_restconf.sock";
+ description
+ "FastCGI unix socket. Should be specified in webserver
+ Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock";
+ }
+ leaf CLICON_RESTCONF_PRETTY {
+ type boolean;
+ default true;
+ description
+ "Restconf return value pretty print.
+ Restconf clients may add HTTP header:
+ Accept: application/yang-data+json, or
+ Accept: application/yang-data+xml
+ to get return value in XML or JSON.
+ RFC 8040 examples print XML and JSON in pretty-printed form.
+ Setting this value to false makes restconf return not pretty-printed
+ which may be desirable for performance or tests";
+ }
+ leaf CLICON_CLI_DIR {
+ type string;
+ description
+ "Location of cli frontend .so plugins. Load all .so
+ plugins in this dir as CLI object plugins";
+ }
+ leaf CLICON_CLISPEC_DIR {
+ type string;
+ description
+ "Location of frontend .cli cligen spec files. Load all .cli
+ files in this dir as CLI specification files";
+ }
+ leaf CLICON_CLISPEC_FILE {
+ type string;
+ description "Specific frontend .cli cligen spec file.";
+ }
+ leaf CLICON_CLI_MODE {
+ type string;
+ default "base";
+ description
+ "Startup CLI mode. This should match a CLICON_MODE set in
+ one of the clispec files";
+ }
+ leaf CLICON_CLI_GENMODEL {
+ type int32;
+ default 1;
+ description
+ "Generate code for CLI completion of existing db symbols.
+ Example: Add name=\"myspec\" in datamodel spec and reference
+ as @myspec";
+ }
+ leaf CLICON_CLI_GENMODEL_COMPLETION {
+ type int32;
+ default 1;
+ description "Generate code for CLI completion of existing db symbols";
+ }
+ leaf CLICON_CLI_GENMODEL_TYPE {
+ type cli_genmodel_type;
+ default "VARS";
+ description "How to generate and show CLI syntax: VARS|ALL";
+ }
+ leaf CLICON_CLI_VARONLY {
+ type int32;
+ default 1;
+ description
+ "Dont include keys in cvec in cli vars callbacks,
+ ie a & k in 'a k ' ignored";
+ }
+ leaf CLICON_CLI_LINESCROLLING {
+ type int32;
+ default 1;
+ description
+ "Set to 0 if you want CLI to wrap to next line.
+ Set to 1 if you want CLI to scroll sideways when approaching
+ right margin";
+ }
+ leaf CLICON_SOCK_FAMILY {
+ type string;
+ default "UNIX";
+ description
+ "Address family for communicating with clixon_backend
+ (UNIX|IPv4|IPv6)";
+ }
+ leaf CLICON_SOCK {
+ type string;
+ mandatory true;
+ description
+ "If family above is AF_UNIX: Unix socket for communicating
+ with clixon_backend. If family is AF_INET: IPv4 address";
+ }
+ leaf CLICON_SOCK_PORT {
+ type int32;
+ default 4535;
+ description
+ "Inet socket port for communicating with clixon_backend
+ (only IPv4|IPv6)";
+ }
+ leaf CLICON_SOCK_GROUP {
+ type string;
+ default "clicon";
+ description "Group membership to access clixon_backend unix socket";
+ }
+ leaf CLICON_BACKEND_PIDFILE {
+ type string;
+ mandatory true;
+ description "Process-id file of backend daemon";
+ }
+ leaf CLICON_AUTOCOMMIT {
+ type int32;
+ default 0;
+ description
+ "Set if all configuration changes are committed automatically
+ on every edit change. Explicit commit commands unnecessary";
+ }
+ leaf CLICON_XMLDB_DIR {
+ type string;
+ mandatory true;
+ description
+ "Directory where \"running\", \"candidate\" and \"startup\" are placed";
+ }
+ leaf CLICON_XMLDB_PLUGIN {
+ type string;
+ mandatory true;
+ description
+ "XMLDB datastore plugin filename
+ (see datastore/ and clixon_xml_db.[ch])";
+ }
+ leaf CLICON_XMLDB_CACHE {
+ type boolean;
+ default true;
+ description
+ "XMLDB datastore cache.
+ If set, XML candidate/running parsed tree is stored in memory
+ If not set, candidate/running is always accessed via disk.";
+ }
+ leaf CLICON_XMLDB_FORMAT {
+ type xmldb_format;
+ default xml;
+ description "XMLDB datastore format.";
+ }
+ leaf CLICON_XMLDB_PRETTY {
+ type boolean;
+ default true;
+ description
+ "XMLDB datastore pretty print.
+ If set, insert spaces and line-feeds making the XML/JSON human
+ readable. If not set, make the XML/JSON more compact.";
+ }
+ leaf CLICON_XML_SORT {
+ type boolean;
+ default true;
+ description
+ "If set, sort XML lists and leaf-lists alphabetically and uses binary
+ search. Unless ordered-by user is used.
+ Only works for Yang specified XML.
+ If not set, all lists accessed via linear search.";
+ }
+ leaf CLICON_USE_STARTUP_CONFIG {
+ type int32;
+ default 0;
+ description
+ "Enabled uses \"startup\" configuration on boot. It is called
+ startup_db and exists in XMLDB_DIR.
+ NOTE: Obsolete with 1.3.3 and CLICON_STARTUP_MODE";
+ }
+ leaf CLICON_STARTUP_MODE {
+ type startup_mode;
+ description "Which method to boot/start clicon backend";
+ }
+ leaf CLICON_TRANSACTION_MOD {
+ type boolean;
+ default false;
+ description "If set, modifications in validation and commit
+ callbacks are written back into the datastore";
+ }
+ leaf CLICON_NACM_MODE {
+ type nacm_mode;
+ default disabled;
+ description "RFC8341 network access configuration control model
+ (NACM) mode: disabled, in regular (internal) config
+ or separate external file given by CLICON_NACM_FILE";
+ }
+ leaf CLICON_NACM_FILE {
+ type string;
+ description "RFC8341 NACM external configuration file";
+ }
+ leaf CLICON_MODULE_LIBRARY_RFC7895 {
+ type boolean;
+ default true;
+ description "Enable RFC 7895 YANG Module library support as state
+ data. If enabled, module info will appear when doing
+ netconf get or restconf GET";
+ }
+ leaf CLICON_MODULE_SET_ID {
+ type string;
+ default "0";
+ description "If RFC 7895 YANG Module library enabled:
+ Contains a server-specific identifier representing
+ the current set of modules and submodules. The
+ server MUST change the value of this leaf if the
+ information represented by the 'module' list instances
+ has changed.";
+ }
+ leaf CLICON_STREAM_DISCOVERY_RFC5277 {
+ type boolean;
+ default false;
+ description "Enable event stream discovery as described in RFC 5277
+ sections 3.2. If enabled, available streams will appear
+ when doing netconf get or restconf GET";
+ }
+ leaf CLICON_STREAM_DISCOVERY_RFC8040 {
+ type boolean;
+ default false;
+ description "Enable event stream discovery as described in RFC 5277
+ sections 3.2. If enabled, available streams will appear
+ when doing netconf get or restconf GET";
+ }
+ leaf CLICON_STREAM_PATH {
+ type string;
+ default "streams";
+ description "Stream path appended to CLICON_STREAM_URL to form
+ stream subscription URL.";
+ }
+ leaf CLICON_STREAM_URL {
+ type string;
+ default "https://localhost";
+ description "Prepend this to CLICON_STREAM_PATH to form URL.
+ See RFC 8040 Sec 9.3 location leaf:
+ 'Contains a URL that represents the entry point for
+ establishing notification delivery via server-sent events.'
+ Prepend this constant to name of stream.
+ Example: https://localhost/streams/NETCONF. Note this is the
+ external URL, not local behind a reverse-proxy.
+ Note that -s command-line option to clixon_restconf
+ should correspond to last path of url (eg 'streams')";
+ }
+ leaf CLICON_STREAM_PUB {
+ type string;
+ description "For stream publish using eg nchan, the base address
+ to publish to. Example value: http://localhost/pub
+ Example: stream NETCONF would then be pushed to
+ http://localhost/pub/NETCONF.
+ Note this may be a local/provate URL behind reverse-proxy.
+ If not given, do NOT enable stream publishing using NCHAN.";
+ }
+ leaf CLICON_STREAM_RETENTION {
+ type uint32;
+ default 3600;
+ units s;
+ description "Retention for stream replay buffers in seconds, ie how much
+ data to store before dropping. 0 means no retention";
+
+ }
+ }
+}
diff --git a/yang/ietf-netconf-monitoring@2010-10-04.yang b/yang/ietf-netconf-monitoring@2010-10-04.yang
new file mode 100644
index 00000000..38a92d06
--- /dev/null
+++ b/yang/ietf-netconf-monitoring@2010-10-04.yang
@@ -0,0 +1,555 @@
+module ietf-netconf-monitoring {
+
+ namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring";
+ prefix "ncm";
+
+ import ietf-yang-types { prefix yang; }
+ import ietf-inet-types { prefix inet; }
+
+ organization
+ "IETF NETCONF (Network Configuration) Working Group";
+
+ contact
+ "WG Web:
+ WG List:
+
+ WG Chair: Mehmet Ersue
+
+
+ WG Chair: Bert Wijnen
+
+
+ Editor: Mark Scott
+
+
+ Editor: Martin Bjorklund
+ ";
+
+ description
+ "NETCONF Monitoring Module.
+ All elements in this module are read-only.
+
+ Copyright (c) 2010 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD
+ License set forth in Section 4.c of the IETF Trust's
+ Legal Provisions Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC 6022; see
+ the RFC itself for full legal notices.";
+
+ revision 2010-10-04 {
+ description
+ "Initial revision.";
+ reference
+ "RFC 6022: YANG Module for NETCONF Monitoring";
+ }
+
+ typedef netconf-datastore-type {
+ type enumeration {
+ enum running;
+ enum candidate;
+ enum startup;
+ }
+ description
+ "Enumeration of possible NETCONF datastore types.";
+ reference
+ "RFC 4741: NETCONF Configuration Protocol";
+ }
+
+ identity transport {
+ description
+ "Base identity for NETCONF transport types.";
+ }
+
+ identity netconf-ssh {
+ base transport;
+ description
+ "NETCONF over Secure Shell (SSH).";
+ reference
+ "RFC 4742: Using the NETCONF Configuration Protocol
+ over Secure SHell (SSH)";
+ }
+
+ identity netconf-soap-over-beep {
+ base transport;
+ description
+ "NETCONF over Simple Object Access Protocol (SOAP) over
+ Blocks Extensible Exchange Protocol (BEEP).";
+ reference
+ "RFC 4743: Using NETCONF over the Simple Object
+ Access Protocol (SOAP)";
+ }
+
+ identity netconf-soap-over-https {
+ base transport;
+ description
+ "NETCONF over Simple Object Access Protocol (SOAP)
+ over Hypertext Transfer Protocol Secure (HTTPS).";
+ reference
+ "RFC 4743: Using NETCONF over the Simple Object
+ Access Protocol (SOAP)";
+ }
+
+ identity netconf-beep {
+ base transport;
+ description
+ "NETCONF over Blocks Extensible Exchange Protocol (BEEP).";
+ reference
+ "RFC 4744: Using the NETCONF Protocol over the
+ Blocks Extensible Exchange Protocol (BEEP)";
+ }
+
+ identity netconf-tls {
+ base transport;
+ description
+ "NETCONF over Transport Layer Security (TLS).";
+ reference
+ "RFC 5539: NETCONF over Transport Layer Security (TLS)";
+ }
+
+ identity schema-format {
+ description
+ "Base identity for data model schema languages.";
+ }
+
+ identity xsd {
+ base schema-format;
+ description
+ "W3C XML Schema Definition.";
+ reference
+ "W3C REC REC-xmlschema-1-20041028:
+ XML Schema Part 1: Structures";
+ }
+
+ identity yang {
+ base schema-format;
+ description
+ "The YANG data modeling language for NETCONF.";
+ reference
+ "RFC 6020: YANG - A Data Modeling Language for the
+ Network Configuration Protocol (NETCONF)";
+ }
+
+ identity yin {
+ base schema-format;
+ description
+ "The YIN syntax for YANG.";
+ reference
+ "RFC 6020: YANG - A Data Modeling Language for the
+ Network Configuration Protocol (NETCONF)";
+ }
+
+ identity rng {
+ base schema-format;
+ description
+ "Regular Language for XML Next Generation (RELAX NG).";
+ reference
+ "ISO/IEC 19757-2:2008: RELAX NG";
+ }
+
+ identity rnc {
+ base schema-format;
+ description
+ "Relax NG Compact Syntax";
+ reference
+ "ISO/IEC 19757-2:2008: RELAX NG";
+ }
+
+ grouping common-counters {
+ description
+ "Counters that exist both per session, and also globally,
+ accumulated from all sessions.";
+
+ leaf in-rpcs {
+ type yang:zero-based-counter32;
+ description
+ "Number of correct messages received.";
+ }
+ leaf in-bad-rpcs {
+ type yang:zero-based-counter32;
+ description
+ "Number of messages received when an message was expected,
+ that were not correct messages. This includes XML parse
+ errors and errors on the rpc layer.";
+ }
+ leaf out-rpc-errors {
+ type yang:zero-based-counter32;
+ description
+ "Number of messages sent that contained an
+ element.";
+ }
+ leaf out-notifications {
+ type yang:zero-based-counter32;
+ description
+ "Number of messages sent.";
+ }
+ }
+
+ container netconf-state {
+ config false;
+ description
+ "The netconf-state container is the root of the monitoring
+ data model.";
+
+ container capabilities {
+ description
+ "Contains the list of NETCONF capabilities supported by the
+ server.";
+
+ leaf-list capability {
+ type inet:uri;
+ description
+ "List of NETCONF capabilities supported by the server.";
+ }
+ }
+
+ container datastores {
+ description
+ "Contains the list of NETCONF configuration datastores.";
+
+ list datastore {
+ key name;
+ description
+ "List of NETCONF configuration datastores supported by
+ the NETCONF server and related information.";
+
+ leaf name {
+ type netconf-datastore-type;
+ description
+ "Name of the datastore associated with this list entry.";
+ }
+ container locks {
+ presence
+ "This container is present only if the datastore
+ is locked.";
+ description
+ "The NETCONF and operations allow
+ a client to lock specific resources in a datastore. The
+ NETCONF server will prevent changes to the locked
+ resources by all sessions except the one that acquired
+ the lock(s).
+
+ Monitoring information is provided for each datastore
+ entry including details such as the session that acquired
+ the lock, the type of lock (global or partial) and the
+ list of locked resources. Multiple locks per datastore
+ are supported.";
+
+ grouping lock-info {
+ description
+ "Lock related parameters, common to both global and
+ partial locks.";
+
+ leaf locked-by-session {
+ type uint32;
+ mandatory true;
+ description
+ "The session ID of the session that has locked
+ this resource. Both a global lock and a partial
+ lock MUST contain the NETCONF session-id.
+
+ If the lock is held by a session that is not managed
+ by the NETCONF server (e.g., a CLI session), a session
+ id of 0 (zero) is reported.";
+ reference
+ "RFC 4741: NETCONF Configuration Protocol";
+ }
+ leaf locked-time {
+ type yang:date-and-time;
+ mandatory true;
+ description
+ "The date and time of when the resource was
+ locked.";
+ }
+ }
+ choice lock-type {
+ description
+ "Indicates if a global lock or a set of partial locks
+ are set.";
+
+ container global-lock {
+ description
+ "Present if the global lock is set.";
+ uses lock-info;
+ }
+
+ list partial-lock {
+ key lock-id;
+ description
+ "List of partial locks.";
+ reference
+ "RFC 5717: Partial Lock Remote Procedure Call (RPC) for
+ NETCONF";
+
+ leaf lock-id {
+ type uint32;
+ description
+ "This is the lock id returned in the
+ response.";
+ }
+ uses lock-info;
+ leaf-list select {
+ type yang:xpath1.0;
+ min-elements 1;
+ description
+ "The xpath expression that was used to request
+ the lock. The select expression indicates the
+ original intended scope of the lock.";
+ }
+ leaf-list locked-node {
+ type instance-identifier;
+ description
+ "The list of instance-identifiers (i.e., the
+ locked nodes).
+
+ The scope of the partial lock is defined by the list
+ of locked nodes.";
+ }
+ }
+ }
+ }
+ }
+ }
+ container schemas {
+ description
+ "Contains the list of data model schemas supported by the
+ server.";
+
+ list schema {
+ key "identifier version format";
+
+ description
+ "List of data model schemas supported by the server.";
+
+ leaf identifier {
+ type string;
+ description
+ "Identifier to uniquely reference the schema. The
+ identifier is used in the operation and may
+ be used for other purposes such as file retrieval.
+
+ For modeling languages that support or require a data
+ model name (e.g., YANG module name) the identifier MUST
+ match that name. For YANG data models, the identifier is
+ the name of the module or submodule. In other cases, an
+ identifier such as a filename MAY be used instead.";
+ }
+ leaf version {
+ type string;
+ description
+ "Version of the schema supported. Multiple versions MAY be
+ supported simultaneously by a NETCONF server. Each
+ version MUST be reported individually in the schema list,
+ i.e., with same identifier, possibly different location,
+ but different version.
+
+ For YANG data models, version is the value of the most
+ recent YANG 'revision' statement in the module or
+ submodule, or the empty string if no 'revision' statement
+ is present.";
+ }
+ leaf format {
+ type identityref {
+ base schema-format;
+ }
+ description
+ "The data modeling language the schema is written
+ in (currently xsd, yang, yin, rng, or rnc).
+ For YANG data models, 'yang' format MUST be supported and
+ 'yin' format MAY also be provided.";
+ }
+ leaf namespace {
+ type inet:uri;
+ mandatory true;
+ description
+ "The XML namespace defined by the data model.
+
+ For YANG data models, this is the module's namespace.
+ If the list entry describes a submodule, this field
+ contains the namespace of the module to which the
+ submodule belongs.";
+ }
+ leaf-list location {
+ type union {
+ type enumeration {
+ enum "NETCONF";
+ }
+ type inet:uri;
+ }
+ description
+ "One or more locations from which the schema can be
+ retrieved. This list SHOULD contain at least one
+ entry per schema.
+
+ A schema entry may be located on a remote file system
+ (e.g., reference to file system for ftp retrieval) or
+ retrieved directly from a server supporting the
+ operation (denoted by the value 'NETCONF').";
+ }
+ }
+ }
+ container sessions {
+ description
+ "The sessions container includes session-specific data for
+ NETCONF management sessions. The session list MUST include
+ all currently active NETCONF sessions.";
+
+ list session {
+ key session-id;
+ description
+ "All NETCONF sessions managed by the NETCONF server
+ MUST be reported in this list.";
+
+ leaf session-id {
+ type uint32 {
+ range "1..max";
+ }
+ description
+ "Unique identifier for the session. This value is the
+ NETCONF session identifier, as defined in RFC 4741.";
+ reference
+ "RFC 4741: NETCONF Configuration Protocol";
+ }
+ leaf transport {
+ type identityref {
+ base transport;
+ }
+ mandatory true;
+ description
+ "Identifies the transport for each session, e.g.,
+ 'netconf-ssh', 'netconf-soap', etc.";
+ }
+ leaf username {
+ type string;
+ mandatory true;
+ description
+ "The username is the client identity that was authenticated
+ by the NETCONF transport protocol. The algorithm used to
+ derive the username is NETCONF transport protocol specific
+ and in addition specific to the authentication mechanism
+ used by the NETCONF transport protocol.";
+ }
+ leaf source-host {
+ type inet:host;
+ description
+ "Host identifier of the NETCONF client. The value
+ returned is implementation specific (e.g., hostname,
+ IPv4 address, IPv6 address)";
+ }
+ leaf login-time {
+ type yang:date-and-time;
+ mandatory true;
+ description
+ "Time at the server at which the session was established.";
+ }
+ uses common-counters {
+ description
+ "Per-session counters. Zero based with following reset
+ behaviour:
+ - at start of a session
+ - when max value is reached";
+ }
+ }
+ }
+
+ container statistics {
+ description
+ "Statistical data pertaining to the NETCONF server.";
+
+ leaf netconf-start-time {
+ type yang:date-and-time;
+ description
+ "Date and time at which the management subsystem was
+ started.";
+ }
+ leaf in-bad-hellos {
+ type yang:zero-based-counter32;
+ description
+ "Number of sessions silently dropped because an
+ invalid message was received. This includes
+ messages with a 'session-id' attribute, bad namespace, and
+ bad capability declarations.";
+ }
+ leaf in-sessions {
+ type yang:zero-based-counter32;
+ description
+ "Number of sessions started. This counter is incremented
+ when a message with a is sent.
+
+ 'in-sessions' - 'in-bad-hellos' =
+ 'number of correctly started netconf sessions'";
+ }
+ leaf dropped-sessions {
+ type yang:zero-based-counter32;
+ description
+ "Number of sessions that were abnormally terminated, e.g.,
+ due to idle timeout or transport close. This counter is not
+ incremented when a session is properly closed by a
+ operation, or killed by a
+ operation.";
+ }
+ uses common-counters {
+ description
+ "Global counters, accumulated from all sessions.
+ Zero based with following reset behaviour:
+ - re-initialization of NETCONF server
+ - when max value is reached";
+ }
+ }
+ }
+
+ rpc get-schema {
+ description
+ "This operation is used to retrieve a schema from the
+ NETCONF server.
+
+ Positive Response:
+ The NETCONF server returns the requested schema.
+
+ Negative Response:
+ If requested schema does not exist, the is
+ 'invalid-value'.
+
+ If more than one schema matches the requested parameters, the
+ is 'operation-failed', and is
+ 'data-not-unique'.";
+
+ input {
+ leaf identifier {
+ type string;
+ mandatory true;
+ description
+ "Identifier for the schema list entry.";
+ }
+ leaf version {
+ type string;
+ description
+ "Version of the schema requested. If this parameter is not
+ present, and more than one version of the schema exists on
+ the server, a 'data-not-unique' error is returned, as
+ described above.";
+ }
+ leaf format {
+ type identityref {
+ base schema-format;
+ }
+ description
+ "The data modeling language of the schema. If this
+ parameter is not present, and more than one formats of
+ the schema exists on the server, a 'data-not-unique' error
+ is returned, as described above.";
+ }
+ }
+ output {
+ anyxml data {
+ description
+ "Contains the schema content.";
+ }
+ }
+ }
+}
diff --git a/yang/ietf-netconf-notification@2008-07-01.yang b/yang/ietf-netconf-notification@2008-07-01.yang
new file mode 100644
index 00000000..978cdd4b
--- /dev/null
+++ b/yang/ietf-netconf-notification@2008-07-01.yang
@@ -0,0 +1,137 @@
+module ietf-netconf-notification {
+ namespace "urn:ietf:params:xml:ns:netconf:notification:1:0";
+ prefix "rcmon";
+
+ import ietf-yang-types { prefix yang; }
+ import ietf-inet-types { prefix inet; }
+
+ organization
+ "IETF NETCONF (Network Configuration) Working Group";
+
+ description
+ "Note this is a translation from RFC 5277 schema in section 4 to Yang.
+ RFC 5277 is Copyright (C) The IETF Trust (2008).";
+
+ revision 2008-07-01 {
+ description
+ "Initial revision.";
+ reference
+ "RFC 5277: NETCONF Event Notifications.";
+ }
+
+ container netconf {
+ config false;
+ description
+ "Contains NETCONF protocol monitoring information.";
+
+ container capabilities {
+ description
+ "Contains a list of protocol capability URIs.";
+
+ leaf-list capability {
+ type inet:uri;
+ description
+ "A RESTCONF protocol capability URI.";
+ }
+ }
+
+ container streams {
+ description
+ "Container representing the notification event streams
+ supported by the server.";
+ reference
+ "RFC 5277, Section 3.4, element.";
+ list stream {
+ key name;
+ description
+ "Each entry describes an event stream supported by
+ the server.";
+
+ leaf name {
+ type string;
+ description
+ "The stream name.";
+ reference
+ "RFC 5277, Section 3.4, element.";
+ }
+
+ leaf description {
+ type string;
+ description
+ "Description of stream content.";
+ reference
+ "RFC 5277, Section 3.4, element.";
+ }
+
+ leaf replay-support {
+ type boolean;
+ default false;
+ description
+ "Indicates if replay buffer is supported for this stream.
+ If 'true', then the server MUST support the 'start-time'
+ and 'stop-time' query parameters for this stream.";
+ reference
+ "RFC 5277, Section 3.4, element.";
+ }
+
+ leaf replay-log-creation-time {
+ when "../replay-support" {
+ description
+ "Only present if notification replay is supported.";
+ }
+ type yang:date-and-time;
+ description
+ "Indicates the time the replay log for this stream
+ was created.";
+ reference
+ "RFC 5277, Section 3.4,
+ element.";
+ }
+
+ }
+ }
+ }
+ rpc create-subscription {
+ description
+ "The command to create a notification subscription. It
+ takes as argument the name of the notification stream
+ and filter. Both of those options
+ limit the content of the subscription. In addition,
+ there are two time-related parameters, startTime and
+ stopTime, which can be used to select the time interval
+ of interest to the notification replay feature.";
+ reference "RFC 5277, Section 2.1";
+ input {
+ leaf stream{
+ type string;
+ default "NETCONF";
+ description "An optional parameter, , that indicates which
+ stream of events is of interest. If not present, events in the
+ default NETCONF stream will be sent.";
+ }
+ leaf filter{
+ type string;
+ description "An optional parameter, , that indicates which
+ subset of all possible events is of interest. The format of this
+ parameter is the same as that of the filter parameter in the
+ NETCONF protocol operations. If not present, all events not
+ precluded by other parameters will be sent. See section 3.6
+ for more information on filters";
+ }
+ leaf startTime {
+ type date-and-time;
+ description "used to trigger the replay feature
+ and indicate that the replay should start at the time
+ specified. If is not present, this is not a replay
+ subscription.";
+ }
+ leaf stopTime {
+ type date-and-time;
+ description "used with the optional
+ replay feature to indicate the newest notifications of
+ interest. If is not present, the notifications will
+ continue until the subscription is terminated.";
+ }
+ }
+ }
+}
diff --git a/yang/ietf-restconf-monitoring@2017-01-26.yang b/yang/ietf-restconf-monitoring@2017-01-26.yang
new file mode 100644
index 00000000..f114c9d0
--- /dev/null
+++ b/yang/ietf-restconf-monitoring@2017-01-26.yang
@@ -0,0 +1,149 @@
+module ietf-restconf-monitoring {
+ namespace "urn:ietf:params:xml:ns:yang:ietf-restconf-monitoring";
+ prefix "rcmon";
+
+ import ietf-yang-types { prefix yang; }
+ import ietf-inet-types { prefix inet; }
+
+ organization
+ "IETF NETCONF (Network Configuration) Working Group";
+
+ contact
+ "WG Web:
+ WG List:
+
+ Author: Andy Bierman
+
+
+ Author: Martin Bjorklund
+
+
+ Author: Kent Watsen
+ ";
+
+ description
+ "This module contains monitoring information for the
+ RESTCONF protocol.
+
+ Copyright (c) 2017 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD License
+ set forth in Section 4.c of the IETF Trust's Legal Provisions
+ Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC 8040; see
+ the RFC itself for full legal notices.";
+
+ revision 2017-01-26 {
+ description
+ "Initial revision.";
+ reference
+ "RFC 8040: RESTCONF Protocol.";
+ }
+
+ container restconf-state {
+ config false;
+ description
+ "Contains RESTCONF protocol monitoring information.";
+
+ container capabilities {
+ description
+ "Contains a list of protocol capability URIs.";
+
+ leaf-list capability {
+ type inet:uri;
+ description
+ "A RESTCONF protocol capability URI.";
+ }
+ }
+
+ container streams {
+ description
+ "Container representing the notification event streams
+ supported by the server.";
+ reference
+ "RFC 5277, Section 3.4, element.";
+
+ list stream {
+ key name;
+ description
+ "Each entry describes an event stream supported by
+ the server.";
+
+ leaf name {
+ type string;
+ description
+ "The stream name.";
+ reference
+ "RFC 5277, Section 3.4, element.";
+ }
+
+ leaf description {
+ type string;
+ description
+ "Description of stream content.";
+ reference
+ "RFC 5277, Section 3.4, element.";
+ }
+
+ leaf replay-support {
+ type boolean;
+ default false;
+ description
+ "Indicates if replay buffer is supported for this stream.
+ If 'true', then the server MUST support the 'start-time'
+ and 'stop-time' query parameters for this stream.";
+ reference
+ "RFC 5277, Section 3.4, element.";
+ }
+
+ leaf replay-log-creation-time {
+ when "../replay-support" {
+ description
+ "Only present if notification replay is supported.";
+ }
+ type yang:date-and-time;
+ description
+ "Indicates the time the replay log for this stream
+ was created.";
+ reference
+ "RFC 5277, Section 3.4,
+ element.";
+ }
+
+ list access {
+ key encoding;
+ min-elements 1;
+ description
+ "The server will create an entry in this list for each
+ encoding format that is supported for this stream.
+ The media type 'text/event-stream' is expected
+ for all event streams. This list identifies the
+ subtypes supported for this stream.";
+
+ leaf encoding {
+ type string;
+ description
+ "This is the secondary encoding format within the
+ 'text/event-stream' encoding used by all streams.
+ The type 'xml' is supported for XML encoding.
+ The type 'json' is supported for JSON encoding.";
+ }
+
+ leaf location {
+ type inet:uri;
+ mandatory true;
+ description
+ "Contains a URL that represents the entry point
+ for establishing notification delivery via
+ server-sent events.";
+ }
+ }
+ }
+ }
+ }
+}