From b41f68b677f71c4f2d7507e1878a58ed0a91cfaa Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 27 Jan 2021 14:40:34 +0100 Subject: [PATCH] debug print of backend state, split test-stream into a netconf and restconf part, change restconf yang auth-type from leaf-list to leaf --- apps/backend/backend_client.c | 11 +- apps/backend/backend_handle.h | 2 + apps/backend/clixon_backend_handle.c | 22 +++ apps/restconf/restconf_main_evhtp.c | 2 +- doc/DEVELOP.md | 2 +- example/main/example_backend.c | 10 +- lib/clixon/clixon_datastore.h | 1 + lib/src/clixon_datastore.c | 29 ++++ test/lib.sh | 1 + test/test_netconf_notifications.sh | 157 ++++++++++++++++++++ test/test_stream.sh | 56 ++----- yang/clixon/clixon-config@2020-12-30.yang | 12 +- yang/clixon/clixon-restconf@2020-10-30.yang | 2 +- 13 files changed, 244 insertions(+), 63 deletions(-) create mode 100755 test/test_netconf_notifications.sh diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 851028ae..315a28e5 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -132,9 +132,9 @@ int backend_client_rm(clicon_handle h, struct client_entry *ce) { - struct client_entry *c; - struct client_entry *c0; - struct client_entry **ce_prev; + struct client_entry *c; + struct client_entry *c0; + struct client_entry **ce_prev; clicon_debug(1, "%s", __FUNCTION__); /* for all streams: XXX better to do it top-level? */ @@ -147,6 +147,7 @@ backend_client_rm(clicon_handle h, clixon_event_unreg_fd(ce->ce_s, from_client); close(ce->ce_s); ce->ce_s = 0; + xmldb_unlock_all(h, ce->ce_id); } break; } @@ -1308,8 +1309,8 @@ from_client_kill_session(clicon_handle h, } /* may or may not be in active client list, probably not */ if ((ce = ce_find_byid(backend_client_list(h), id)) != NULL){ - xmldb_unlock_all(h, id); - backend_client_rm(h, ce); + xmldb_unlock_all(h, id); /* Removes locks on all databases */ + backend_client_rm(h, ce); /* Removes client struct */ } if (xmldb_islocked(h, db) == id) xmldb_unlock(h, db); diff --git a/apps/backend/backend_handle.h b/apps/backend/backend_handle.h index 3433a21b..37e57f44 100644 --- a/apps/backend/backend_handle.h +++ b/apps/backend/backend_handle.h @@ -54,4 +54,6 @@ struct client_entry *backend_client_list(clicon_handle h); int backend_client_delete(clicon_handle h, struct client_entry *ce); +int backend_client_print(clicon_handle h, FILE *f); + #endif /* _BACKEND_HANDLE_H_ */ diff --git a/apps/backend/clixon_backend_handle.c b/apps/backend/clixon_backend_handle.c index 05bf313d..dd9714de 100644 --- a/apps/backend/clixon_backend_handle.c +++ b/apps/backend/clixon_backend_handle.c @@ -188,3 +188,25 @@ backend_client_delete(clicon_handle h, return 0; } +/*! Debug print backend clients + * @param[in] h Clicon handle + * @param[in] f UNIX output stream + */ +int +backend_client_print(clicon_handle h, + FILE *f) +{ + struct backend_handle *bh = handle(h); + struct client_entry *ce; + + for (ce = bh->bh_ce_list; ce; ce = ce->ce_next){ + fprintf(f, "Client: %d\n", ce->ce_nr); + fprintf(f, " Session: %d\n", ce->ce_id); + fprintf(f, " Socket: %d\n", ce->ce_s); + fprintf(f, " Msgs in: %d\n", ce->ce_stat_in); + fprintf(f, " Msgs out: %d\n", ce->ce_stat_out); + fprintf(f, " Username: %s\n", ce->ce_username); + } + return 0; +} + diff --git a/apps/restconf/restconf_main_evhtp.c b/apps/restconf/restconf_main_evhtp.c index 395ab1af..1f616bb7 100644 --- a/apps/restconf/restconf_main_evhtp.c +++ b/apps/restconf/restconf_main_evhtp.c @@ -946,7 +946,7 @@ cx_evhtp_init(clicon_handle h, /* If at least one socket has ssl then enable global ssl_enable */ ssl_enable = xpath_first(xrestconf, nsc, "socket[ssl='true']") != NULL; /* get common fields */ - if ((x = xpath_first(xrestconf, nsc, "auth-type")) != NULL) /* XXX: leaf-list? */ + if ((x = xpath_first(xrestconf, nsc, "auth-type")) != NULL) auth_type = xml_body(x); if (auth_type && strcmp(auth_type, "client-certificate") == 0) auth_type_client_certificate = 1; diff --git a/doc/DEVELOP.md b/doc/DEVELOP.md index ba00a9eb..0dee9f2c 100644 --- a/doc/DEVELOP.md +++ b/doc/DEVELOP.md @@ -148,7 +148,7 @@ Merge a branch back: ## Use of constants etc -Use MAXPATHLEN (not PATH_MAX) +Use MAXPATHLEN (not PATH_MAX) in sys/param.h ## Emulating a serial console diff --git a/example/main/example_backend.c b/example/main/example_backend.c index 918d83ad..a364d886 100644 --- a/example/main/example_backend.c +++ b/example/main/example_backend.c @@ -66,7 +66,7 @@ #include /* Command line options to be passed to getopt(3) */ -#define BACKEND_EXAMPLE_OPTS "rsS:iuUt:v:n:" +#define BACKEND_EXAMPLE_OPTS "rsS:iuUt:v:" /*! Variable to control if reset code is run. * The reset code inserts "extra XML" which assumes ietf-interfaces is @@ -119,11 +119,6 @@ static int _transaction_log = 0; static char *_validate_fail_xpath = NULL; static int _validate_fail_toggle = 0; /* fail at validate and commit */ -/* -n - * Network namespace for starting restconf process in another namespace -*/ -static char *_proc_netns = NULL; - /* forward */ static int example_stream_timer_setup(clicon_handle h); @@ -1087,9 +1082,6 @@ clixon_plugin_init(clicon_handle h) case 'v': /* validate fail */ _validate_fail_xpath = optarg; break; - case 'n': /* process restconf namespace*/ - _proc_netns = optarg; - break; } /* Example stream initialization: diff --git a/lib/clixon/clixon_datastore.h b/lib/clixon/clixon_datastore.h index 3203f399..6aaecb78 100644 --- a/lib/clixon/clixon_datastore.h +++ b/lib/clixon/clixon_datastore.h @@ -75,5 +75,6 @@ int xmldb_modified_get(clicon_handle h, const char *db); int xmldb_modified_set(clicon_handle h, const char *db, int value); int xmldb_empty_get(clicon_handle h, const char *db); int xmldb_dump(clicon_handle h, FILE *f, cxobj *xt); +int xmldb_print(clicon_handle h, FILE *f); #endif /* _CLIXON_DATASTORE_H */ diff --git a/lib/src/clixon_datastore.c b/lib/src/clixon_datastore.c index f3cc54f7..31b6a29f 100644 --- a/lib/src/clixon_datastore.c +++ b/lib/src/clixon_datastore.c @@ -560,3 +560,32 @@ xmldb_modified_set(clicon_handle h, de->de_modified = value; return 0; } + +/* Print the datastore meta-info to file + */ +int +xmldb_print(clicon_handle h, + FILE *f) +{ + int retval = -1; + db_elmnt *de = NULL; + char **keys = NULL; + size_t klen; + int i; + + if (clicon_hash_keys(clicon_db_elmnt(h), &keys, &klen) < 0) + goto done; + for (i = 0; i < klen; i++){ + /* XXX name */ + if ((de = clicon_db_elmnt_get(h, keys[i])) == NULL) + continue; + fprintf(f, "Datastore: %s\n", keys[i]); + fprintf(f, " Session: %u\n", de->de_id); + fprintf(f, " XML: %p\n", de->de_xml); + fprintf(f, " Modified: %d\n", de->de_modified); + fprintf(f, " Empty: %d\n", de->de_empty); + } + retval = 0; + done: + return retval; +} diff --git a/test/lib.sh b/test/lib.sh index e467ece8..eb565eef 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -334,6 +334,7 @@ stop_restconf(){ # Wait for restconf to stop sending 502 Bad Gateway # @see start_restconf # Reasons for not working: if you run evhtp is nginx running? +# @note assumes port=80 if RCPROTO=http and port=443 if RCPROTO=https wait_restconf(){ # echo "curl $CURLOPTS $* $RCPROTO://localhost/restconf" hdr=$(curl $CURLOPTS $* $RCPROTO://localhost/restconf) 2> /dev/null diff --git a/test/test_netconf_notifications.sh b/test/test_netconf_notifications.sh new file mode 100755 index 00000000..9f4bd619 --- /dev/null +++ b/test/test_netconf_notifications.sh @@ -0,0 +1,157 @@ +#!/usr/bin/env bash +# Tests for Netconf event streams using notifications +# See RFC5277 NETCONF Event Notifications +# +# 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 + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example + +NCWAIT=10 # Wait (netconf valgrind may need more time) + +# Ensure UTC +DATE=$(date -u +"%Y-%m-%d") + +cfg=$dir/conf.xml +fyang=$dir/stream.yang +xml=$dir/xml.xml + +# example +cat < $cfg + + $cfg + /usr/local/share/clixon + $IETFRFC + $fyang + false + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/lib/$APPNAME/backend + example_backend.so$ + $dir/restconf.pidfile + /usr/local/var/$APPNAME + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + true + true + streams + 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 "urn:example:clixon"; + 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 + +new "test params: -f $cfg" + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg" + start_backend -s init -f $cfg +fi + +new "waiting" +wait_backend + +# +# 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 -D $DBG -qf $cfg" 0 "]]>]]>" "EXAMPLEExample event streamtrue]]>]]>" + +new "netconf EXAMPLE subscription" +expectwait "$clixon_netconf -D $DBG -qf $cfg" "EXAMPLE]]>]]>" "^]]>]]>20" $NCWAIT + +new "netconf subscription with empty startTime" +expecteof "$clixon_netconf -D $DBG -qf $cfg" 0 "EXAMPLE]]>]]>" "^applicationbad-elementstartTimeerrorregexp match fail:" + +new "netconf EXAMPLE subscription with simple filter" +expectwait "$clixon_netconf -D $DBG -qf $cfg" "EXAMPLE]]>]]>" "^]]>]]>20" $NCWAIT + +new "netconf EXAMPLE subscription with filter classifier" +expectwait "$clixon_netconf -D $DBG -qf $cfg" "EXAMPLE]]>]]>" "^]]>]]>20" $NCWAIT + +new "netconf NONEXIST subscription" +expectwait "$clixon_netconf -D $DBG -qf $cfg" "NONEXIST]]>]]>" "^applicationinvalid-valueerrorNo such stream]]>]]>$" $NCWAIT + +new "netconf EXAMPLE subscription with wrong date" +expectwait "$clixon_netconf -D $DBG -qf $cfg" "EXAMPLEkallekaka]]>]]>" "^applicationbad-elementstartTimeerrorregexp match fail:" 0 + +#new "netconf EXAMPLE subscription with replay" +#NOW=$(date +"%Y-%m-%dT%H:%M:%S") +#sleep 10 +#expectwait "$clixon_netconf -D $DBG -qf $cfg" "EXAMPLE$NOW]]>]]>" "^]]>]]>20" 10 + +if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg +fi + +rm -rf $dir + diff --git a/test/test_stream.sh b/test/test_stream.sh index 0876ebe8..1836e048 100755 --- a/test/test_stream.sh +++ b/test/test_stream.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash # Tests for event streams using notifications +# See RFC5277 NETCONF Event Notifications +# RFC8040 Sec 6.2 # Assumptions: # 1. http server setup, such as nginx described in apps/restconf/README.md # especially SSE - ngchan setup @@ -134,43 +136,13 @@ if [ $RC -ne 0 ]; then wait_restconf fi -# -# 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 -D $DBG -qf $cfg" 0 "]]>]]>" "EXAMPLEExample event streamtrue]]>]]>" - new "netconf event stream discovery RFC8040 Sec 6.2" expecteof "$clixon_netconf -D $DBG -qf $cfg" 0 "]]>]]>" "EXAMPLEExample event streamtruexmlhttps://localhost/streams/EXAMPLE]]>]]>" # # 1.2 Netconf stream subscription -new "netconf EXAMPLE subscription" -expectwait "$clixon_netconf -D $DBG -qf $cfg" "EXAMPLE]]>]]>" "^]]>]]>20" $NCWAIT -new "netconf subscription with empty startTime" -expecteof "$clixon_netconf -D $DBG -qf $cfg" 0 "EXAMPLE]]>]]>" "^applicationbad-elementstartTimeerrorregexp match fail:" -new "netconf EXAMPLE subscription with simple filter" -expectwait "$clixon_netconf -D $DBG -qf $cfg" "EXAMPLE]]>]]>" "^]]>]]>20" $NCWAIT - -new "netconf EXAMPLE subscription with filter classifier" -expectwait "$clixon_netconf -D $DBG -qf $cfg" "EXAMPLE]]>]]>" "^]]>]]>20" $NCWAIT - -new "netconf NONEXIST subscription" -expectwait "$clixon_netconf -D $DBG -qf $cfg" "NONEXIST]]>]]>" "^applicationinvalid-valueerrorNo such stream]]>]]>$" $NCWAIT - -new "netconf EXAMPLE subscription with wrong date" -expectwait "$clixon_netconf -D $DBG -qf $cfg" "EXAMPLEkallekaka]]>]]>" "^applicationbad-elementstartTimeerrorregexp match fail:" 0 - -#new "netconf EXAMPLE subscription with replay" -#NOW=$(date +"%Y-%m-%dT%H:%M:%S") -#sleep 10 -#expectwait "$clixon_netconf -D $DBG -qf $cfg" "EXAMPLE$NOW]]>]]>" "^]]>]]>20" 10 -sleep 1 - -# # 2. Restconf RFC8040 stream testing new "2. Restconf RFC8040 stream testing" # 2.1 Stream discovery @@ -261,8 +233,8 @@ if [ -z "$match" ]; then fi nr=$(echo "$ret" | grep -c "data:") -if [ $nr -lt 9 -o $nr -gt 14 ]; then - err "9-14" "$nr" +if [ $nr -lt 8 -o $nr -gt 14 ]; then + err "8-14" "$nr" fi sleep 1 @@ -300,19 +272,17 @@ if [ $RC -ne 0 ]; then stop_restconf fi -if [ $BE -eq 0 ]; then - exit # BE +if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg fi -new "Kill backend" -# Check if premature kill -pid=$(pgrep -u root -f clixon_backend) -if [ -z "$pid" ]; then - err "backend already dead" -fi -# kill backend -stop_backend -f $cfg - rm -rf $dir # unset conditional parameters diff --git a/yang/clixon/clixon-config@2020-12-30.yang b/yang/clixon/clixon-config@2020-12-30.yang index fd8d409f..1c59730e 100644 --- a/yang/clixon/clixon-config@2020-12-30.yang +++ b/yang/clixon/clixon-config@2020-12-30.yang @@ -300,9 +300,11 @@ module clixon-config { enum IPv4 { description "IPv4"; } + enum IPv6 { + description "IPv6"; + } } } - container clixon-config { container restconf { uses clrc:clixon-restconf; @@ -629,8 +631,12 @@ module clixon-config { 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"; + "String description of Clixon Internal (IPC) socket that connects a clixon + client to the clixon backend. This string is dependent on family. + If CLICON_SOCK_FAMILY is: + - UNIX: The value is a Unix socket path + - IPv4: IPv4 address string + - IPv6: IPv6 address string (NYI)"; } leaf CLICON_SOCK_PORT { type int32; diff --git a/yang/clixon/clixon-restconf@2020-10-30.yang b/yang/clixon/clixon-restconf@2020-10-30.yang index 9344d855..ed6cb9df 100644 --- a/yang/clixon/clixon-restconf@2020-10-30.yang +++ b/yang/clixon/clixon-restconf@2020-10-30.yang @@ -78,7 +78,7 @@ module clixon-restconf { For example, if the restconf daemon is under systemd management, the restconf daemon will only start if enable=true."; } - leaf-list auth-type { + leaf auth-type { type http-auth-type; description "The authentication type.