RESTCONF notification for native mode

This commit is contained in:
Olof hagsand 2024-05-19 17:44:00 +02:00
parent 7a842846db
commit 1336a4ccfb
14 changed files with 282 additions and 533 deletions

View file

@ -15,6 +15,9 @@ Expected: June 2024
### Features ### Features
* RESTCONF notification for native mode
* Previously only for FCGI
* Limitations, ie only HTTP/1, regular subscription + stop-time
* Optimization of yang schema mount: share yang-specs if equal * Optimization of yang schema mount: share yang-specs if equal
* Changed datastore modstate to be last in file, as prior to 7.0 * Changed datastore modstate to be last in file, as prior to 7.0
* New: Event priority. Backend socket has higher prio * New: Event priority. Backend socket has higher prio

View file

@ -88,7 +88,6 @@ LEX = @LEX@
CPPFLAGS = @CPPFLAGS@ CPPFLAGS = @CPPFLAGS@
ifeq ($(LINKAGE),dynamic) ifeq ($(LINKAGE),dynamic)
CPPFLAGS += -fPIC CPPFLAGS += -fPIC
endif endif
@ -100,7 +99,6 @@ APPL = clixon_restconf
# Common source - not accessible from plugin - independent of restconf package (fcgi|native) # Common source - not accessible from plugin - independent of restconf package (fcgi|native)
APPSRC = APPSRC =
APPSRC += restconf_api.c # maybe empty
APPSRC += restconf_err.c APPSRC += restconf_err.c
APPSRC += restconf_methods.c APPSRC += restconf_methods.c
APPSRC += restconf_methods_post.c APPSRC += restconf_methods_post.c

View file

@ -4,17 +4,25 @@
* [Nginx](#nginx) * [Nginx](#nginx)
* [Streams](#streams) * [Streams](#streams)
* [Nchan Streams](#nchan) * [Nchan Streams](#nchan)
* [Debugging](#debugging) * [Debugging](#debugging)
There are two installation instructions: for native and nginx. There are two installation instructions: for native and nginx.
## Native ## Native
Native with http1 and http2 is the main variant, with most regression testing.
Configure clixon with native restconf: Configure clixon with native restconf:
``` ```
./configure --with-restconf=native ./configure --with-restconf=native
``` ```
You can disable http1 and http2:
```
--disable-http1 Disable native http/1.1 (ie http/2 only)
--disable-nghttp2 Disable native http/2 using libnghttp2 (ie http/1 only)
```
Ensure www-data is member of the CLICON_SOCK_GROUP (default clicon). If not, add it: Ensure www-data is member of the CLICON_SOCK_GROUP (default clicon). If not, add it:
``` ```
sudo usermod -a -G clicon www-data sudo usermod -a -G clicon www-data
@ -164,6 +172,8 @@ See (stream tests)[../test/test_streams.sh] for more examples.
## Nchan ## Nchan
This is not supported
As an alternative streams implementation, Nginx/Nchan can be used. As an alternative streams implementation, Nginx/Nchan can be used.
Nginx uses pub/sub channels and can be configured in a variety of Nginx uses pub/sub channels and can be configured in a variety of
ways. The following uses a simple variant with one generic subscription ways. The following uses a simple variant with one generic subscription
@ -247,3 +257,16 @@ You can set debug level of the backend via restconf:
``` ```
curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-lib:input":{"level":1}}' http://localhost/restconf/operations/clixon-lib:debug curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-lib:input":{"level":1}}' http://localhost/restconf/operations/clixon-lib:debug
``` ```
## Code structure
Due to the native and fcgi variants, and also native http1/http2, the
source file structure is complex.
There are the following blocks of files:
* COMMON: Common code, such as HTTP methods processing, error and common lib functions
* FCGI: Top-level main, stream and low-level lib (as defined by restconf_api.h)
* NATIVE-COMMON: Top-level main, stream and low-level lib
* NATIVE-HTTP1: Native for HTTP/1 only
* NATIVE-HTTP2: Native for Libnghttp2 only

View file

@ -1,67 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
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 *****
* Generic restconf API functions
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <time.h>
#include <limits.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <libgen.h>
#include <sys/stat.h> /* chmod */
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include <clixon/clixon.h>
/* restconf */
#include "restconf_lib.h"
#include "restconf_api.h"
/* empty */

View file

@ -67,9 +67,7 @@
#include "clixon_http1_parse.h" #include "clixon_http1_parse.h"
#include "restconf_http1.h" #include "restconf_http1.h"
#include "clixon_http_data.h" #include "clixon_http_data.h"
#ifdef RESTCONF_NATIVE_STREAM
#include "restconf_stream.h" #include "restconf_stream.h"
#endif
/* Size of xml read buffer */ /* Size of xml read buffer */
#define BUFLEN 1024 #define BUFLEN 1024
@ -453,13 +451,11 @@ restconf_http1_path_root(clixon_handle h,
if (api_http_data(h, sd, sd->sd_qvec) < 0) if (api_http_data(h, sd, sd->sd_qvec) < 0)
goto done; goto done;
} }
#ifdef RESTCONF_NATIVE_STREAM
else if (api_path_is_stream(h)){ else if (api_path_is_stream(h)){
restconf_socket *rs = rc->rc_socket; restconf_socket *rs = rc->rc_socket;
if (api_stream(h, sd, sd->sd_qvec, rs->rs_stream_timeout, NULL) < 0) if (api_stream(h, sd, sd->sd_qvec, rs->rs_stream_timeout, NULL) < 0)
goto done; goto done;
} }
#endif
else else
sd->sd_code = 404; /* catch all without body/media */ sd->sd_code = 404; /* catch all without body/media */
fail: fail:

View file

@ -76,9 +76,7 @@
#ifdef HAVE_HTTP1 #ifdef HAVE_HTTP1
#include "restconf_http1.h" #include "restconf_http1.h"
#endif #endif
#ifdef RESTCONF_NATIVE_STREAM
#include "restconf_stream.h" #include "restconf_stream.h"
#endif
/* Forward */ /* Forward */
static int restconf_idle_cb(int fd, void *arg); static int restconf_idle_cb(int fd, void *arg);
@ -384,7 +382,8 @@ restconf_connection_sanity(clixon_handle h,
} }
/* Write buf to socket /* Write buf to socket
* see also this function in restcont_api_openssl.c *
* see also this function in restconf_api_openssl.c
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] buf Buffer to write * @param[in] buf Buffer to write
* @param[in] buflen Length of buffer * @param[in] buflen Length of buffer
@ -394,7 +393,7 @@ restconf_connection_sanity(clixon_handle h,
* @retval 0 OK, but socket write returned error, caller should close rc * @retval 0 OK, but socket write returned error, caller should close rc
* @retval -1 Error * @retval -1 Error
*/ */
static int int
native_buf_write(clixon_handle h, native_buf_write(clixon_handle h,
char *buf, char *buf,
size_t buflen, size_t buflen,
@ -1081,11 +1080,9 @@ restconf_connection_close1(restconf_conn *rc)
if (restconf_callhome_timer(rsock, 1) < 0) if (restconf_callhome_timer(rsock, 1) < 0)
goto done; goto done;
} }
#ifdef RESTCONF_NATIVE_STREAM
if (rc->rc_event_stream){ if (rc->rc_event_stream){
stream_close(rc->rc_h, rc); stream_close(rc->rc_h, rc);
} }
#endif
retval = 0; retval = 0;
done: done:
clixon_debug(CLIXON_DBG_RESTCONF, "retval:%d", retval); clixon_debug(CLIXON_DBG_RESTCONF, "retval:%d", retval);

View file

@ -121,9 +121,7 @@ typedef struct restconf_conn {
restconf_socket *rc_socket; /* Backpointer to restconf_socket needed for callhome */ restconf_socket *rc_socket; /* Backpointer to restconf_socket needed for callhome */
struct timeval rc_t; /* Timestamp of last read/write activity, used by callhome struct timeval rc_t; /* Timestamp of last read/write activity, used by callhome
idle-timeout algorithm */ idle-timeout algorithm */
#ifdef RESTCONF_NATIVE_STREAM
int rc_event_stream; /* Event notification stream socket (maybe in sd?) */ int rc_event_stream; /* Event notification stream socket (maybe in sd?) */
#endif
} restconf_conn; } restconf_conn;
/* Restconf per socket handle /* Restconf per socket handle
@ -182,6 +180,7 @@ int ssl_x509_name_oneline(SSL *ssl, char **oneline);
int restconf_close_ssl_socket(restconf_conn *rc, const char *callfn, int sslerr0); int restconf_close_ssl_socket(restconf_conn *rc, const char *callfn, int sslerr0);
int restconf_connection_sanity(clixon_handle h, restconf_conn *rc, restconf_stream_data *sd); int restconf_connection_sanity(clixon_handle h, restconf_conn *rc, restconf_stream_data *sd);
int native_buf_write(clixon_handle h, char *buf, size_t buflen, restconf_conn *rc, const char *callfn);
restconf_native_handle *restconf_native_handle_get(clixon_handle h); restconf_native_handle *restconf_native_handle_get(clixon_handle h);
int restconf_connection(int s, void *arg); int restconf_connection(int s, void *arg);
int restconf_ssl_accept_client(clixon_handle h, int s, restconf_socket *rsock, restconf_conn **rcp); int restconf_ssl_accept_client(clixon_handle h, int s, restconf_socket *rsock, restconf_conn **rcp);
@ -190,6 +189,8 @@ int restconf_callhome_timer(restconf_socket *rsock, int status);
int restconf_socket_extract(clixon_handle h, cxobj *xs, cvec *nsc, restconf_socket *rsock, int restconf_socket_extract(clixon_handle h, cxobj *xs, cvec *nsc, restconf_socket *rsock,
char **namespace, char **address, char **addrtype, uint16_t *port); char **namespace, char **address, char **addrtype, uint16_t *port);
#endif /* _RESTCONF_NATIVE_H_ */ #endif /* _RESTCONF_NATIVE_H_ */
#ifdef __cplusplus #ifdef __cplusplus

View file

@ -85,9 +85,7 @@
#include "restconf_err.h" #include "restconf_err.h"
#include "restconf_root.h" #include "restconf_root.h"
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/ #include "restconf_native.h" /* Restconf-openssl mode specific headers*/
#ifdef RESTCONF_NATIVE_STREAM
#include "restconf_stream.h" #include "restconf_stream.h"
#endif
#ifdef HAVE_LIBNGHTTP2 /* Ends at end-of-file */ #ifdef HAVE_LIBNGHTTP2 /* Ends at end-of-file */
#include "restconf_nghttp2.h" /* Restconf-openssl mode specific headers*/ #include "restconf_nghttp2.h" /* Restconf-openssl mode specific headers*/
#include "clixon_http_data.h" #include "clixon_http_data.h"
@ -341,13 +339,11 @@ restconf_nghttp2_path(restconf_stream_data *sd)
if (api_http_data(h, sd, sd->sd_qvec) < 0) if (api_http_data(h, sd, sd->sd_qvec) < 0)
goto done; goto done;
} }
#ifdef RESTCONF_NATIVE_STREAM
else if (api_path_is_stream(h)){ else if (api_path_is_stream(h)){
restconf_socket *rs = rc->rc_socket; restconf_socket *rs = rc->rc_socket;
if (api_stream(h, sd, sd->sd_qvec, rs->rs_stream_timeout, NULL) < 0) if (api_stream(h, sd, sd->sd_qvec, rs->rs_stream_timeout, NULL) < 0)
goto done; goto done;
} }
#endif
else if (api_root_restconf(h, sd, sd->sd_qvec) < 0) /* error handling */ else if (api_root_restconf(h, sd, sd->sd_qvec) < 0) /* error handling */
goto done; goto done;
} }
@ -497,9 +493,7 @@ http2_exec(restconf_conn *rc,
if (strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0 if (strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0
|| api_path_is_restconf(rc->rc_h) || api_path_is_restconf(rc->rc_h)
|| api_path_is_data(rc->rc_h) || api_path_is_data(rc->rc_h)
#ifdef RESTCONF_NATIVE_STREAM
|| api_path_is_stream(rc->rc_h) || api_path_is_stream(rc->rc_h)
#endif
) { ) {
clixon_debug(CLIXON_DBG_RESTCONF, "path found"); clixon_debug(CLIXON_DBG_RESTCONF, "path found");
if (restconf_nghttp2_path(sd) < 0) if (restconf_nghttp2_path(sd) < 0)

View file

@ -179,6 +179,8 @@ restconf_subscription(clixon_handle h,
goto ok; goto ok;
} }
/* Setting up stream */ /* Setting up stream */
if (restconf_reply_header(req, "Server", "clixon") < 0)
goto done;
if (restconf_reply_header(req, "Server", "clixon") < 0) if (restconf_reply_header(req, "Server", "clixon") < 0)
goto done; goto done;
if (restconf_reply_header(req, "Content-Type", "text/event-stream") < 0) if (restconf_reply_header(req, "Content-Type", "text/event-stream") < 0)
@ -187,10 +189,9 @@ restconf_subscription(clixon_handle h,
goto done; goto done;
if (restconf_reply_header(req, "Connection", "keep-alive") < 0) if (restconf_reply_header(req, "Connection", "keep-alive") < 0)
goto done; goto done;
#ifndef RESTCONF_NATIVE_STREAM /* Must be there for FCGI caching */
if (restconf_reply_header(req, "X-Accel-Buffering", "no") < 0) if (restconf_reply_header(req, "X-Accel-Buffering", "no") < 0)
goto done; goto done;
#endif
if (restconf_reply_send(req, 201, NULL, 0) < 0) if (restconf_reply_send(req, 201, NULL, 0) < 0)
goto done; goto done;
*sp = s; *sp = s;
@ -204,3 +205,120 @@ restconf_subscription(clixon_handle h,
cbuf_free(cb); cbuf_free(cb);
return retval; return retval;
} }
/*! Process a stream request
*
* @param[in] h Clixon handle
* @param[in] req Generic Www handle (can be part of clixon handle)
* @param[in] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
* @param[in] timeout Stream timeout
* @param[out] finish Set to zero, if request should not be finnished by upper layer
* @retval 0 OK
* @retval -1 Error
*/
int
api_stream(clixon_handle h,
void *req,
cvec *qvec,
int timeout,
int *finish)
{
int retval = -1;
char *path = NULL;
char **pvec = NULL;
int pn;
cvec *pcvec = NULL; /* for rest api */
cxobj *xerr = NULL;
char *streampath;
int pretty;
int besock = -1;
restconf_media media_reply = YANG_DATA_XML;
char *media_str = NULL;
char *stream_name;
int ret;
clixon_debug(CLIXON_DBG_STREAM, "");
if (req == NULL){
clixon_err(OE_RESTCONF, EINVAL, "req is NULL");
goto done;
}
streampath = clicon_option_str(h, "CLICON_STREAM_PATH");
if ((path = restconf_uripath(h)) == NULL)
goto done;
pretty = restconf_pretty_get(h);
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
goto done;
/* Sanity check of path. Should be /stream/<name> */
if (pn != 3){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
goto done;
goto ok;
}
/* Get media for output (proactive negotiation) RFC7231 by using
*/
media_str = restconf_param_get(h, "HTTP_ACCEPT");
if (media_str == NULL){
if (restconf_not_acceptable(h, req, pretty, media_reply) < 0)
goto done;
goto ok;
}
/* Accept only text_event-stream or */
if (strcmp(media_str, "*/*") != 0 &&
strcmp(media_str, "text/event-stream") != 0){
if (restconf_not_acceptable(h, req, pretty, media_reply) < 0)
goto done;
goto ok;
}
if (strlen(pvec[0]) != 0){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
goto done;
goto ok;
}
if (strcmp(pvec[1], streampath)){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
goto done;
goto ok;
}
if ((stream_name = pvec[2]) == NULL){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
goto done;
goto ok;
}
clixon_debug(CLIXON_DBG_STREAM, "stream-name: %s", stream_name);
if (uri_str2cvec(path, '/', '=', 1, &pcvec) < 0) /* rest url eg /album=ricky/foo */
goto done;
/* If present, check credentials. See "plugin_credentials" in plugin
* See RFC 8040 section 2.5
*/
if ((ret = restconf_authentication_cb(h, req, pretty, media_reply)) < 0)
goto done;
if (ret == 0)
goto ok;
if (restconf_subscription(h, req, stream_name, qvec, pretty, media_reply, &besock) < 0)
goto done;
if (besock != -1){
if (stream_sockets_setup(h, req, timeout, besock, finish) < 0)
goto done;
}
ok:
retval = 0;
done:
clixon_debug(CLIXON_DBG_STREAM, "retval:%d", retval);
if (xerr)
xml_free(xerr);
if (pvec)
free(pvec);
if (pcvec)
cvec_free(pcvec);
if (path)
free(path);
return retval;
}

View file

@ -42,9 +42,10 @@
*/ */
int api_path_is_stream(clixon_handle h); int api_path_is_stream(clixon_handle h);
int restconf_subscription(clixon_handle h, void *req, char *name, cvec *qvec, int pretty, restconf_media media_out, int *sp); int restconf_subscription(clixon_handle h, void *req, char *name, cvec *qvec, int pretty, restconf_media media_out, int *sp);
int stream_child_free(clixon_handle h, int pid);
int stream_child_freeall(clixon_handle h);
int stream_close(clixon_handle h, void *req);
int api_stream(clixon_handle h, void *req, cvec *qvec, int timeout, int *finish); int api_stream(clixon_handle h, void *req, cvec *qvec, int timeout, int *finish);
int stream_sockets_setup(clixon_handle h, void *req, int timeout, int besock, int *finish);
int stream_close(clixon_handle h, void *req); // only native
int stream_child_free(clixon_handle h, int pid); // only fcgi
int stream_child_freeall(clixon_handle h); // only fcgi
#endif /* _RESTCONF_STREAM_H_ */ #endif /* _RESTCONF_STREAM_H_ */

View file

@ -104,8 +104,9 @@
*/ */
/* Enable for forking stream subscription loop. /* Enable for forking stream subscription loop.
* Disable to get single threading but blocking on streams * Disable to get single threading but blocking on streams
* XXX: Integrate with top-level events
*/ */
#define STREAM_FORK #undef STREAM_FORK
/* Keep track of children - when they exit - their FCGX handle needs to be /* Keep track of children - when they exit - their FCGX handle needs to be
* freed with FCGX_Free(&rbk, 0); * freed with FCGX_Free(&rbk, 0);
@ -120,6 +121,8 @@ struct stream_child{
*/ */
static struct stream_child *STREAM_CHILD = NULL; static struct stream_child *STREAM_CHILD = NULL;
static int backend_eof = 0;
/*! Find restconf child using PID and cleanup FCGI Request data /*! Find restconf child using PID and cleanup FCGI Request data
* *
* For forked, called on SIGCHILD * For forked, called on SIGCHILD
@ -174,11 +177,11 @@ stream_child_freeall(clixon_handle h)
* @see netconf_notification_cb * @see netconf_notification_cb
*/ */
static int static int
restconf_stream_cb(int s, stream_fcgi_backend_cb(int s,
void *arg) void *arg)
{ {
int retval = -1; int retval = -1;
FCGX_Request *r = (FCGX_Request *)arg; FCGX_Request *req = (FCGX_Request *)arg;
int eof; int eof;
cxobj *xtop = NULL; /* top xml */ cxobj *xtop = NULL; /* top xml */
cxobj *xn; /* notification xml */ cxobj *xn; /* notification xml */
@ -193,14 +196,13 @@ restconf_stream_cb(int s,
clixon_debug(CLIXON_DBG_STREAM, "%s", cbuf_get(cbmsg)); // Also MSG clixon_debug(CLIXON_DBG_STREAM, "%s", cbuf_get(cbmsg)); // Also MSG
/* handle close from remote end: this will exit the client */ /* handle close from remote end: this will exit the client */
if (eof){ if (eof){
clixon_debug(CLIXON_DBG_STREAM, "eof"); clixon_debug(CLIXON_DBG_STREAM, "eof, terminate stream");
clixon_err(OE_PROTO, ESHUTDOWN, "Socket unexpected close"); backend_eof = 1;
errno = ESHUTDOWN; clixon_exit_set(1); // local timeout
FCGX_FPrintF(r->out, "SHUTDOWN\r\n"); FCGX_FPrintF(req->out, "SHUTDOWN\r\n");
FCGX_FPrintF(r->out, "\r\n"); FCGX_FPrintF(req->out, "\r\n");
FCGX_FFlush(r->out); FCGX_FFlush(req->out);
clixon_exit_set(1); goto ok;
goto done;
} }
if ((ret = clixon_xml_parse_string(cbuf_get(cbmsg), YB_NONE, NULL, &xtop, NULL)) < 0) if ((ret = clixon_xml_parse_string(cbuf_get(cbmsg), YB_NONE, NULL, &xtop, NULL)) < 0)
goto done; goto done;
@ -229,9 +231,9 @@ restconf_stream_cb(int s,
#endif #endif
if (clixon_xml2cbuf(cb, xn, 0, pretty, NULL, -1, 0) < 0) if (clixon_xml2cbuf(cb, xn, 0, pretty, NULL, -1, 0) < 0)
goto done; goto done;
FCGX_FPrintF(r->out, "data: %s\r\n", cbuf_get(cb)); FCGX_FPrintF(req->out, "data: %s\r\n", cbuf_get(cb));
FCGX_FPrintF(r->out, "\r\n"); FCGX_FPrintF(req->out, "\r\n");
FCGX_FFlush(r->out); FCGX_FFlush(req->out);
ok: ok:
retval = 0; retval = 0;
done: done:
@ -251,8 +253,8 @@ restconf_stream_cb(int s,
* @param[in] req Generic Www handle (can be part of clixon handle) * @param[in] req Generic Www handle (can be part of clixon handle)
*/ */
static int static int
stream_checkuplink(int s, stream_fcgi_uplink_cb(int s,
void *arg) void *arg)
{ {
FCGX_Request *r = (FCGX_Request *)arg; FCGX_Request *r = (FCGX_Request *)arg;
@ -289,191 +291,106 @@ fcgi_stream_timeout(int s,
/*! Timeout of notification stream, limit lifetime, for debug /*! Timeout of notification stream, limit lifetime, for debug
*/ */
static int static int
fcgi_stream_timeout2(int s, stream_timeout_end(int s,
void *arg) void *arg)
{ {
clixon_debug(CLIXON_DBG_STREAM, "Terminate stream"); clixon_debug(CLIXON_DBG_STREAM, "Terminate stream");
clixon_exit_set(1); // XXX This is local eventloop see below, not global clixon_exit_set(1); // XXX This is local eventloop see below, not global
return 0; return 0;
} }
/*! Process a stream request /*! FCGI specific code for setting up stream sockets
* *
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] req Generic Www handle (can be part of clixon handle) * @param[in] req Generic Www handle (can be part of clixon handle)
* @param[in] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
* @param[in] timeout Stream timeout * @param[in] timeout Stream timeout
* @param[in] besock Socket to backend
* @param[out] finish Set to zero, if request should not be finnished by upper layer * @param[out] finish Set to zero, if request should not be finnished by upper layer
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* Consider moving timeout and backend sock to generic code
*/ */
int int
api_stream(clixon_handle h, stream_sockets_setup(clixon_handle h,
void *req, void *req,
cvec *qvec, int timeout,
int timeout, int besock,
int *finish) int *finish)
{ {
int retval = -1; int retval = -1;
FCGX_Request *rfcgi = (FCGX_Request *)req; /* XXX */ FCGX_Request *rfcgi = (FCGX_Request *)req; /* XXX */
char *path = NULL;
char *method;
char **pvec = NULL;
int pn;
cvec *pcvec = NULL; /* for rest api */
cbuf *cb = NULL;
char *indata;
int pretty;
restconf_media media_out = YANG_DATA_XML; /* XXX default */
cbuf *cbret = NULL;
int s = -1;
int ret;
cxobj *xerr = NULL;
char *streampath;
#ifdef STREAM_FORK #ifdef STREAM_FORK
int pid; int pid;
struct stream_child *sc; struct stream_child *sc;
if ((pid = fork()) == 0){ /* child */
#if 0 // Leaks
if (pvec)
free(pvec);
if (qvec)
cvec_free(qvec);
if (pcvec)
cvec_free(pcvec);
#endif #endif
clixon_debug(CLIXON_DBG_STREAM, "");
streampath = clicon_option_str(h, "CLICON_STREAM_PATH");
if ((path = restconf_uripath(h)) == NULL)
goto done;
pretty = restconf_pretty_get(h);
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
goto done;
/* Sanity check of path. Should be /stream/<name> */
if (pn != 3){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if (strlen(pvec[0]) != 0){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if (strcmp(pvec[1], streampath)){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if ((method = pvec[2]) == NULL){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
clixon_debug(CLIXON_DBG_STREAM, "method=%s", method);
if (uri_str2cvec(path, '/', '=', 1, &pcvec) < 0) /* rest url eg /album=ricky/foo */
goto done;
/* data */
if ((cb = restconf_get_indata(req)) == NULL)
goto done;
indata = cbuf_get(cb);
clixon_debug(CLIXON_DBG_STREAM, "DATA=%s", indata);
/* If present, check credentials. See "plugin_credentials" in plugin
* See RFC 8040 section 2.5
*/
if ((ret = restconf_authentication_cb(h, req, pretty, media_out)) < 0)
goto done;
if (ret == 0)
goto ok;
if (restconf_subscription(h, req, method, qvec, pretty, media_out, &s) < 0)
goto done;
if (s != -1){
#ifdef STREAM_FORK
if ((pid = fork()) == 0){ /* child */
if (pvec)
free(pvec);
if (qvec)
cvec_free(qvec);
if (pcvec)
cvec_free(pcvec);
if (cb)
cbuf_free(cb);
if (cbret)
cbuf_free(cbret);
#endif /* STREAM_FORK */ #endif /* STREAM_FORK */
/* Listen to backend socket */ backend_eof = 0;
if (clixon_event_reg_fd(s, /* Listen to backend socket */
restconf_stream_cb, if (clixon_event_reg_fd(besock,
req, stream_fcgi_backend_cb,
"stream socket") < 0) req,
"stream socket") < 0)
goto done;
if (clixon_event_reg_fd(rfcgi->listen_sock,
stream_fcgi_uplink_cb,
req,
"stream socket") < 0)
goto done;
/* Timeout of notification stream, close after limited lifetime, for debug */
if (timeout){
struct timeval t;
gettimeofday(&t, NULL);
t.tv_sec += timeout;
clixon_event_reg_timeout(t, stream_timeout_end, req, "Stream timeout");
}
/* Poll upstream errors */
fcgi_stream_timeout(0, req);
/* Start loop */
clixon_event_loop(h);
clixon_debug(CLIXON_DBG_STREAM, "after loop");
if (backend_eof == 0)
if (clicon_rpc_close_session(h) < 0)
goto done; goto done;
if (clixon_event_reg_fd(rfcgi->listen_sock, clixon_event_unreg_fd(besock, stream_fcgi_backend_cb);
stream_checkuplink, close(besock);
req, clixon_event_unreg_fd(rfcgi->listen_sock, stream_fcgi_uplink_cb);
"stream socket") < 0) clixon_event_unreg_timeout(fcgi_stream_timeout, (void*)req);
goto done; clixon_event_unreg_timeout(stream_timeout_end, (void*)req);
/* Timeout of notification stream, close after limited lifetime, for debug */ clixon_exit_set(0); /* reset */
if (timeout){
struct timeval t;
gettimeofday(&t, NULL);
t.tv_sec += timeout;
clixon_event_reg_timeout(t, fcgi_stream_timeout2, req, "Stream timeout");
}
/* Poll upstream errors */
fcgi_stream_timeout(0, req);
/* Start loop */
clixon_event_loop(h);
clixon_debug(CLIXON_DBG_STREAM, "after loop");
clicon_rpc_close_session(h);
clixon_event_unreg_fd(s, restconf_stream_cb);
close(s);
clixon_event_unreg_fd(rfcgi->listen_sock,
restconf_stream_cb);
clixon_event_unreg_timeout(fcgi_stream_timeout, (void*)req);
clixon_event_unreg_timeout(fcgi_stream_timeout2, (void*)req);
clixon_exit_set(0); /* reset */
#ifdef STREAM_FORK #ifdef STREAM_FORK
#if 0 /* Seems to be a global resource, but there is till some timing error here */ #if 0 /* Seems to be a global resource, but there is till some timing error here */
FCGX_Finish_r(rfcgi); FCGX_Finish_r(rfcgi);
FCGX_Free(rfcgi, 0); FCGX_Free(rfcgi, 0);
#endif #endif
restconf_terminate(h); restconf_terminate(h);
exit(0); 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){
clixon_err(OE_XML, errno, "malloc");
goto done;
}
memset(sc, 0, sizeof(struct stream_child));
sc->sc_pid = pid;
sc->sc_r = *rfcgi; /* XXX by value */
ADDQ(sc, STREAM_CHILD);
*finish = 0; /* If spawn child, we should not finish this stream */
#endif /* STREAM_FORK */
} }
ok: /* 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){
clixon_err(OE_XML, errno, "malloc");
goto done;
}
memset(sc, 0, sizeof(struct stream_child));
sc->sc_pid = pid;
sc->sc_r = *rfcgi; /* XXX by value */
ADDQ(sc, STREAM_CHILD);
*finish = 0; /* If spawn child, we should not finish this stream */
#endif /* STREAM_FORK */
retval = 0; retval = 0;
done: done:
clixon_debug(CLIXON_DBG_STREAM, "retval:%d", retval); clixon_debug(CLIXON_DBG_STREAM, "retval:%d", retval);
if (xerr)
xml_free(xerr);
if (pvec)
free(pvec);
if (pcvec)
cvec_free(pcvec);
if (cb)
cbuf_free(cb);
if (cbret)
cbuf_free(cbret);
if (path)
free(path);
return retval; return retval;
} }

View file

@ -86,101 +86,6 @@
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/ #include "restconf_native.h" /* Restconf-openssl mode specific headers*/
#include "restconf_stream.h" #include "restconf_stream.h"
// XXX: copy from restconf_native.c
static int
native_buf_write_xxx(clixon_handle h,
char *buf,
size_t buflen,
restconf_conn *rc,
const char *callfn)
{
int retval = -1;
ssize_t len;
ssize_t totlen = 0;
int er;
SSL *ssl;
if (rc == NULL){
clixon_err(OE_RESTCONF, EINVAL, "rc is NULL");
goto done;
}
ssl = rc->rc_ssl;
/* Two problems with debugging buffers that this fixes:
* 1. they are not "strings" in the sense they are not NULL-terminated
* 2. they are often very long
*/
if (clixon_debug_get()) {
char *dbgstr = NULL;
size_t sz;
sz = buflen>256?256:buflen; /* Truncate to 256 */
if ((dbgstr = malloc(sz+1)) == NULL){
clixon_err(OE_UNIX, errno, "malloc");
goto done;
}
memcpy(dbgstr, buf, sz);
dbgstr[sz] = '\0';
clixon_debug(CLIXON_DBG_RESTCONF, "%s buflen:%zu buf:\n%s", callfn, buflen, dbgstr);
free(dbgstr);
}
while (totlen < buflen){
if (ssl){
if ((len = SSL_write(ssl, buf+totlen, buflen-totlen)) <= 0){
er = errno;
switch (SSL_get_error(ssl, len)){
case SSL_ERROR_SYSCALL: /* 5 */
if (er == ECONNRESET || /* Connection reset by peer */
er == EPIPE) { /* Reading end of socket is closed */
goto closed; /* Close socket and ssl */
}
else if (er == EAGAIN){
clixon_debug(CLIXON_DBG_RESTCONF, "write EAGAIN");
usleep(10000);
continue;
}
else{
clixon_err(OE_RESTCONF, er, "SSL_write %d", er);
goto done;
}
break;
default:
clixon_err(OE_SSL, 0, "SSL_write");
goto done;
break;
}
goto done;
}
}
else{
if ((len = write(rc->rc_s, buf+totlen, buflen-totlen)) < 0){
switch (errno){
case EAGAIN: /* Operation would block */
clixon_debug(CLIXON_DBG_RESTCONF, "write EAGAIN");
usleep(10000);
continue;
break;
// case EBADF: // XXX if this happens there is some larger error
case ECONNRESET: /* Connection reset by peer */
case EPIPE: /* Broken pipe */
goto closed; /* Close socket and ssl */
break;
default:
clixon_err(OE_UNIX, errno, "write %d", errno);
goto done;
break;
}
}
}
totlen += len;
} /* while */
retval = 1;
done:
clixon_debug(CLIXON_DBG_RESTCONF, "retval:%d", retval);
return retval;
closed:
retval = 0;
goto done;
}
/*! Callback when stream notifications arrive from backend /*! Callback when stream notifications arrive from backend
* *
* @param[in] s Socket * @param[in] s Socket
@ -190,8 +95,8 @@ native_buf_write_xxx(clixon_handle h,
* @see netconf_notification_cb * @see netconf_notification_cb
*/ */
static int static int
restconf_native_stream_cb(int s, stream_native_backend_cb(int s,
void *arg) void *arg)
{ {
int retval = -1; int retval = -1;
restconf_stream_data *sd = (restconf_stream_data *)arg; restconf_stream_data *sd = (restconf_stream_data *)arg;
@ -258,7 +163,7 @@ restconf_native_stream_cb(int s,
cprintf(cb, "\r\n"); cprintf(cb, "\r\n");
cprintf(cb, "\r\n"); cprintf(cb, "\r\n");
#endif #endif
if ((ret = native_buf_write_xxx(h, cbuf_get(cb), cbuf_len(cb), rc, "native stream")) < 0) if ((ret = native_buf_write(h, cbuf_get(cb), cbuf_len(cb), rc, "native stream")) < 0)
goto done; goto done;
ok: ok:
retval = 0; retval = 0;
@ -278,8 +183,8 @@ restconf_native_stream_cb(int s,
/*! Timeout of notification stream, limit lifetime, for debug /*! Timeout of notification stream, limit lifetime, for debug
*/ */
static int static int
native_stream_timeout(int s, stream_timeout_end(int s,
void *arg) void *arg)
{ {
restconf_conn *rc = (restconf_conn *)arg; restconf_conn *rc = (restconf_conn *)arg;
@ -299,170 +204,52 @@ stream_close(clixon_handle h,
restconf_conn *rc = (restconf_conn *)req; restconf_conn *rc = (restconf_conn *)req;
clicon_rpc_close_session(h); clicon_rpc_close_session(h);
clixon_event_unreg_fd(rc->rc_event_stream, restconf_native_stream_cb); clixon_event_unreg_fd(rc->rc_event_stream, stream_native_backend_cb);
clixon_event_unreg_timeout(native_stream_timeout, req); clixon_event_unreg_timeout(stream_timeout_end, req);
close(rc->rc_event_stream); close(rc->rc_event_stream);
rc->rc_event_stream = 0; rc->rc_event_stream = 0;
return 0; return 0;
} }
/*! Process a stream request, native variant /*! Native specific code for setting up stream sockets
* *
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] req Generic Www handle (can be part of clixon handle) * @param[in] req Generic Www handle (can be part of clixon handle)
* @param[in] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
* @param[in] timeout Stream timeout * @param[in] timeout Stream timeout
* @param[out] finish Not used in native? * @param[in] besock Socket to backend
* @param[out] finish Set to zero, if request should not be finnished by upper layer
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* @see api_stream fcgi implementation * Consider moving timeout and backend sock to generic code
* @note According to RFC8040 Sec 6 accept-stream is text/event-stream, but stream data
* is XML according to RFC5277. But what is error return? assume XML here
*/ */
static int int
api_native_stream(clixon_handle h, stream_sockets_setup(clixon_handle h,
void *req, void *req,
cvec *qvec, int timeout,
int timeout, int besock,
int *finish) int *finish)
{ {
int retval = -1; int retval = -1;
restconf_stream_data *sd = (restconf_stream_data *)req; restconf_stream_data *sd = (restconf_stream_data *)req;
restconf_conn *rc; restconf_conn *rc;
char *path = NULL;
char *request_method = NULL; /* GET,.. */
char *streampath;
int pretty;
char **pvec = NULL;
int pn;
cvec *pcvec = NULL; /* for rest api */
cxobj *xerr = NULL;
char *media_str = NULL;
char *stream_name;
restconf_media media_reply = YANG_DATA_XML;
int ret;
int backend_socket = -1;
clixon_debug(CLIXON_DBG_STREAM, ""); /* Listen to backend socket */
if (req == NULL){ if (clixon_event_reg_fd(besock,
clixon_err(OE_RESTCONF, EINVAL, "req is NULL"); stream_native_backend_cb,
req,
"stream socket") < 0)
goto done; goto done;
}
rc = sd->sd_conn; rc = sd->sd_conn;
streampath = clicon_option_str(h, "CLICON_STREAM_PATH"); rc->rc_event_stream = besock;
if ((path = restconf_uripath(h)) == NULL) /* Timeout of notification stream, close after limited lifetime, for debug */
goto done; if (timeout){
clixon_debug(CLIXON_DBG_STREAM, "path:%s", path); struct timeval t;
request_method = restconf_param_get(h, "REQUEST_METHOD"); gettimeofday(&t, NULL);
clixon_debug(CLIXON_DBG_STREAM, "method:%s", request_method); t.tv_sec += timeout;
pretty = restconf_pretty_get(h); clixon_event_reg_timeout(t, stream_timeout_end, rc, "Stream timeout");
clixon_debug(CLIXON_DBG_STREAM, "pretty:%d", pretty);
/* Get media for output (proactive negotiation) RFC7231 by using
* Accept:. This is for methods that have output, such as GET,
* operation POST, etc
* If accept is * default is yang-json
*/
media_str = restconf_param_get(h, "HTTP_ACCEPT");
clixon_debug(CLIXON_DBG_STREAM, "accept(media):%s", media_str);
if (media_str == NULL){
if (restconf_not_acceptable(h, sd, pretty, media_reply) < 0)
goto done;
goto ok;
} }
/* Accept only text_event-stream or */
if (strcmp(media_str, "*/*") != 0 &&
strcmp(media_str, "text/event-stream") != 0){
if (restconf_not_acceptable(h, req, pretty, media_reply) < 0)
goto done;
goto ok;
}
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
goto done;
if (strlen(pvec[0]) != 0){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
goto done;
goto ok;
}
else if (strcmp(pvec[1], streampath)){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
goto done;
goto ok;
}
else if ((stream_name = pvec[2]) == NULL ||
strlen(stream_name) == 0){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
goto done;
goto ok;
}
clixon_debug(CLIXON_DBG_STREAM, "stream-name: %s", stream_name);
if (uri_str2cvec(path, '/', '=', 1, &pcvec) < 0) /* rest url eg /album=ricky/foo */
goto done;
/* If present, check credentials. See "plugin_credentials" in plugin
* See RFC 8040 section 2.5
*/
if ((ret = restconf_authentication_cb(h, req, pretty, media_reply)) < 0)
goto done;
if (ret == 0)
goto ok;
clixon_debug(CLIXON_DBG_STREAM, "passed auth");
if (restconf_subscription(h, req, stream_name, qvec, pretty, media_reply, &backend_socket) < 0)
goto done;
if (backend_socket != -1){
// XXX Could add forking here eventurally
/* Listen to backend socket */
if (clixon_event_reg_fd(backend_socket,
restconf_native_stream_cb,
sd,
"stream socket") < 0)
goto done;
rc->rc_event_stream = backend_socket;
/* Timeout of notification stream, close after limited lifetime, for debug */
if (timeout){
struct timeval t;
gettimeofday(&t, NULL);
t.tv_sec += timeout;
clixon_event_reg_timeout(t, native_stream_timeout, rc, "Stream timeout");
}
}
ok:
retval = 0; retval = 0;
done: done:
clixon_debug(CLIXON_DBG_STREAM, "retval:%d", retval); clixon_debug(CLIXON_DBG_STREAM, "retval:%d", retval);
if (xerr)
xml_free(xerr);
if (path)
free(path);
if (pvec)
free(pvec);
if (pcvec)
cvec_free(pcvec);
return retval; return retval;
} }
/*! Process a stream request, native variant
*
* @param[in] h Clixon handle
* @param[in] req Generic Www handle (can be part of clixon handle)
* @param[in] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
* @param[out] finish Not used in native?
* @retval 0 OK
* @retval -1 Error
* @see api_stream fcgi implementation
* @note According to RFC8040 Sec 6 accept-stream is text/event-stream, but stream data
* is XML according to RFC5277. But what is error return? assume XML here
*/
int
api_stream(clixon_handle h,
void *req,
cvec *qvec,
int timeout,
int *finish)
{
return api_native_stream(h, req, qvec, timeout, finish);
}

View file

@ -212,10 +212,6 @@
*/ */
#undef USE_SHA256 #undef USE_SHA256
/*! Restconf native stream support
*/
#define RESTCONF_NATIVE_STREAM
/*! Temporary comparison of xyanglibs, should be removed asap /*! Temporary comparison of xyanglibs, should be removed asap
*/ */
#define YANG_SCHEMA_CMP_KLUDGE #define YANG_SCHEMA_CMP_KLUDGE

View file

@ -133,7 +133,6 @@ function runtest()
new "2a) start $extra timeout:${TIMEOUT}s - expect ${LBOUND}-${UBOUND} notifications" new "2a) start $extra timeout:${TIMEOUT}s - expect ${LBOUND}-${UBOUND} notifications"
ret=$(curl $CURLOPTS $extra -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" $RCPROTO://localhost/streams/EXAMPLE) ret=$(curl $CURLOPTS $extra -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" $RCPROTO://localhost/streams/EXAMPLE)
match=$(echo "$ret" | grep -Eo "$expect") match=$(echo "$ret" | grep -Eo "$expect")
if [ -z "$match" ]; then if [ -z "$match" ]; then
err "$expect" "$ret" err "$expect" "$ret"
@ -149,7 +148,6 @@ function runtest()
new "2b) start $extra timeout:${TIMEOUT} stop after 5s - expect ${LB}-${UB} notifications" new "2b) start $extra timeout:${TIMEOUT} stop after 5s - expect ${LB}-${UB} notifications"
ret=$(curl $CURLOPTS $extra -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" $RCPROTO://localhost/streams/EXAMPLE?stop-time=${time1}) ret=$(curl $CURLOPTS $extra -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" $RCPROTO://localhost/streams/EXAMPLE?stop-time=${time1})
match=$(echo "$ret" | grep -Eo "$expect") match=$(echo "$ret" | grep -Eo "$expect")
if [ -z "$match" ]; then if [ -z "$match" ]; then
err "$expect" "$ret" err "$expect" "$ret"
@ -181,6 +179,9 @@ if [ $BE -ne 0 ]; then
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
err err
fi fi
sudo pkill -f clixon_backend # to be sure
fi
if [ $BE -ne 0 ]; then
new "start backend -s init -f $cfg -- -n ${PERIOD}" new "start backend -s init -f $cfg -- -n ${PERIOD}"
# create example notification stream with periodic timeout ${PERIOD} seconds # create example notification stream with periodic timeout ${PERIOD} seconds
start_backend -s init -f $cfg -- -n ${PERIOD} start_backend -s init -f $cfg -- -n ${PERIOD}
@ -192,7 +193,8 @@ wait_backend
if [ $RC -ne 0 ]; then if [ $RC -ne 0 ]; then
new "kill old restconf daemon" new "kill old restconf daemon"
stop_restconf_pre stop_restconf_pre
sleep 1
new "start restconf daemon -f $cfg -t ${TIMEOUT}" new "start restconf daemon -f $cfg -t ${TIMEOUT}"
start_restconf -f $cfg -t ${TIMEOUT} start_restconf -f $cfg -t ${TIMEOUT}
fi fi
@ -233,23 +235,6 @@ if [ "${WITH_RESTCONF}" = "native" ]; then
fi fi
if false; then # NYI if false; then # NYI
test-pause
# 2b) start subscription 8s - stoptime after 5s - expect 1-2 notifications
new "2b) start subscriptions 8s - stoptime after 5s - expect 1-2 notifications"
ret=$($clixon_util_stream -u $RCPROTO://localhost/streams/EXAMPLE -t 8 -e +10)
expect="data: <notification xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\"><eventTime>${DATE}T[0-9:.]*Z</eventTime><event xmlns=\"urn:example:clixon\"><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event>"
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
test-pause
# 2c # 2c
new "2c) start sub 8s - replay from start -8s - expect 3-4 notifications" new "2c) start sub 8s - replay from start -8s - expect 3-4 notifications"
ret=$($clixon_util_stream -u $RCPROTO://localhost/streams/EXAMPLE -t 10 -s -8) ret=$($clixon_util_stream -u $RCPROTO://localhost/streams/EXAMPLE -t 10 -s -8)