* Multi-socket feature (eg IPv4/IPv6 http/https) to restconf evhtp
* Added by-ref parameter to `ys_cv_validate()` returning which sub-yang spec was validated in a union.
This commit is contained in:
parent
707685f5ff
commit
6eb18da5e9
13 changed files with 683 additions and 382 deletions
|
|
@ -29,6 +29,7 @@
|
||||||
|
|
||||||
### New features
|
### New features
|
||||||
|
|
||||||
|
* Initial NBMA functionality (thanks: @benavrhm): "ds" resource
|
||||||
* Restconf configuration has a new configure model: `clixon-restconf.yang` enabling restconf daemon configuration from datastore instead of from config file.
|
* Restconf configuration has a new configure model: `clixon-restconf.yang` enabling restconf daemon configuration from datastore instead of from config file.
|
||||||
* Restconf config data, such as addresses, authentication type, etc, is read from the backend datastore instead of the clixon-config file on startup.
|
* Restconf config data, such as addresses, authentication type, etc, is read from the backend datastore instead of the clixon-config file on startup.
|
||||||
* This is enabled by setting `CLIXON_RESTCONF_CONFIG` to true (or start clixon-restconf with `-b`), in which case restconf data can be set in the datastore.
|
* This is enabled by setting `CLIXON_RESTCONF_CONFIG` to true (or start clixon-restconf with `-b`), in which case restconf data can be set in the datastore.
|
||||||
|
|
@ -48,6 +49,7 @@ Users may have to change how they access the system
|
||||||
|
|
||||||
Developers may need to change their code
|
Developers may need to change their code
|
||||||
|
|
||||||
|
* Added by-ref parameter to `ys_cv_validate()` returning which sub-yang spec was validated in a union.
|
||||||
* Changed first parameter from `int fd` to `FILE *f` in the following functions:
|
* Changed first parameter from `int fd` to `FILE *f` in the following functions:
|
||||||
* clixon_xml_parse_file(), clixon_json_parse_file(), yang_parse_file()
|
* clixon_xml_parse_file(), clixon_json_parse_file(), yang_parse_file()
|
||||||
* See [Bytewise read() of files is slow #146](https://github.com/clicon/clixon/issues/146)
|
* See [Bytewise read() of files is slow #146](https://github.com/clicon/clixon/issues/146)
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@
|
||||||
|
|
||||||
/*! Open an INET stream socket and bind it to a file descriptor
|
/*! Open an INET stream socket and bind it to a file descriptor
|
||||||
*
|
*
|
||||||
o * @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] dst IPv4 address (see inet_pton(3))
|
* @param[in] dst IPv4 address (see inet_pton(3))
|
||||||
* @retval s Socket file descriptor (see socket(2))
|
* @retval s Socket file descriptor (see socket(2))
|
||||||
* @retval -1 Error
|
* @retval -1 Error
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,9 @@
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <sys/stat.h> /* chmod */
|
#include <sys/stat.h> /* chmod */
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
/* evhtp */
|
/* evhtp */
|
||||||
#include <evhtp/evhtp.h>
|
#include <evhtp/evhtp.h>
|
||||||
|
|
@ -88,8 +91,9 @@
|
||||||
|
|
||||||
/* clixon evhtp handle */
|
/* clixon evhtp handle */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
evhtp_t *eh_htp;
|
evhtp_t **eh_htpvec; /* One per socket */
|
||||||
struct event_base *eh_evbase;
|
int eh_htplen; /* Number of sockets */
|
||||||
|
struct event_base *eh_evbase; /* Change to list */
|
||||||
evhtp_ssl_cfg_t *eh_ssl_config;
|
evhtp_ssl_cfg_t *eh_ssl_config;
|
||||||
} cx_evhtp_handle;
|
} cx_evhtp_handle;
|
||||||
|
|
||||||
|
|
@ -105,12 +109,16 @@ static void
|
||||||
evhtp_terminate(cx_evhtp_handle *eh)
|
evhtp_terminate(cx_evhtp_handle *eh)
|
||||||
{
|
{
|
||||||
evhtp_ssl_cfg_t *sc;
|
evhtp_ssl_cfg_t *sc;
|
||||||
|
int i;
|
||||||
|
|
||||||
if (eh == NULL)
|
if (eh == NULL)
|
||||||
return;
|
return;
|
||||||
if (eh->eh_htp){
|
if (eh->eh_htpvec){
|
||||||
evhtp_unbind_socket(eh->eh_htp);
|
for (i=0; i<eh->eh_htplen; i++){
|
||||||
evhtp_free(eh->eh_htp);
|
evhtp_unbind_socket(eh->eh_htpvec[i]);
|
||||||
|
evhtp_free(eh->eh_htpvec[i]);
|
||||||
|
}
|
||||||
|
free(eh->eh_htpvec);
|
||||||
}
|
}
|
||||||
if (eh->eh_evbase)
|
if (eh->eh_evbase)
|
||||||
event_base_free(eh->eh_evbase);
|
event_base_free(eh->eh_evbase);
|
||||||
|
|
@ -554,7 +562,7 @@ cx_get_ssl_server_certs(clicon_handle h,
|
||||||
* @retval -1 Error
|
* @retval -1 Error
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
cx_get_ssl_client_certs(clicon_handle h,
|
cx_get_ssl_client_ca_certs(clicon_handle h,
|
||||||
const char *server_ca_cert_path,
|
const char *server_ca_cert_path,
|
||||||
evhtp_ssl_cfg_t *ssl_config)
|
evhtp_ssl_cfg_t *ssl_config)
|
||||||
{
|
{
|
||||||
|
|
@ -643,7 +651,7 @@ static int
|
||||||
cx_verify_certs(int pre_verify,
|
cx_verify_certs(int pre_verify,
|
||||||
evhtp_x509_store_ctx_t *store)
|
evhtp_x509_store_ctx_t *store)
|
||||||
{
|
{
|
||||||
#ifdef NOTYET
|
#if 0 //def NOTYET
|
||||||
char buf[256];
|
char buf[256];
|
||||||
X509 * err_cert;
|
X509 * err_cert;
|
||||||
int err;
|
int err;
|
||||||
|
|
@ -667,6 +675,88 @@ cx_verify_certs(int pre_verify,
|
||||||
return pre_verify;
|
return pre_verify;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
*
|
||||||
|
* @param[out] addr Address as string, eg "0.0.0.0", "::"
|
||||||
|
* @param[in] addrtype One of inet:ipv4-address or inet:ipv6-address
|
||||||
|
* @param[out] ss Server socket (bound for accept)
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
restconf_socket_init(clicon_handle h,
|
||||||
|
const char *addr,
|
||||||
|
const char *addrtype,
|
||||||
|
uint16_t port,
|
||||||
|
int *ss)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
int s = -1;
|
||||||
|
struct sockaddr * sa;
|
||||||
|
struct sockaddr_in6 sin6 = { 0 };
|
||||||
|
struct sockaddr_in sin = { 0 };
|
||||||
|
size_t sin_len;
|
||||||
|
int on = 1;
|
||||||
|
|
||||||
|
if (strcmp(addrtype, "inet:ipv6-address") == 0) {
|
||||||
|
sin_len = sizeof(struct sockaddr_in6);
|
||||||
|
sin6.sin6_port = htons(port);
|
||||||
|
sin6.sin6_family = AF_INET6;
|
||||||
|
|
||||||
|
evutil_inet_pton(AF_INET6, addr, &sin6.sin6_addr);
|
||||||
|
sa = (struct sockaddr *)&sin6;
|
||||||
|
}
|
||||||
|
else if (strcmp(addrtype, "inet:ipv4-address") == 0) {
|
||||||
|
sin_len = sizeof(struct sockaddr_in);
|
||||||
|
sin.sin_family = AF_INET;
|
||||||
|
sin.sin_port = htons(port);
|
||||||
|
sin.sin_addr.s_addr = inet_addr(addr);
|
||||||
|
|
||||||
|
sa = (struct sockaddr *)&sin;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
clicon_err(OE_XML, EINVAL, "Unexpected addrtype: %s", addrtype);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
/* create inet socket */
|
||||||
|
if ((s = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) {
|
||||||
|
clicon_err(OE_UNIX, errno, "socket");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
// evutil_make_socket_closeonexec(s); // XXX
|
||||||
|
// evutil_make_socket_nonblocking(s); // XXX
|
||||||
|
|
||||||
|
if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)) == -1) {
|
||||||
|
clicon_err(OE_UNIX, errno, "setsockopt SO_KEEPALIVE");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)) == -1) {
|
||||||
|
clicon_err(OE_UNIX, errno, "setsockopt SO_REUSEADDR");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* only bind ipv6, otherwise it may bind to ipv4 as well which is strange but seems default */
|
||||||
|
if (sa->sa_family == AF_INET6 &&
|
||||||
|
setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) == -1) {
|
||||||
|
clicon_err(OE_UNIX, errno, "setsockopt IPPROTO_IPV6");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (bind(s, sa, sin_len) == -1) {
|
||||||
|
clicon_err(OE_UNIX, errno, "bind port %u", port);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (listen(s, SOCKET_LISTEN_BACKLOG) < 0){
|
||||||
|
clicon_err(OE_UNIX, errno, "listen");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (ss)
|
||||||
|
*ss = s;
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
if (retval != 0 && s != -1)
|
||||||
|
evutil_closesocket(s);
|
||||||
|
return retval;
|
||||||
|
// return evhtp_bind_sockaddr(htp, sa, sin_len, SOCKET_LISTEN_BACKLOG);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*! Usage help routine
|
/*! Usage help routine
|
||||||
* @param[in] argv0 command line
|
* @param[in] argv0 command line
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
|
|
@ -701,23 +791,23 @@ usage(clicon_handle h,
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Main routine for libevhtp restconf
|
/*! Extract socket info from backend config
|
||||||
*/
|
|
||||||
/*! Phase 2 of start per-socket config
|
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] xs socket config
|
* @param[in] xs socket config
|
||||||
* @param[in] nsc Namespace context
|
* @param[in] nsc Namespace context
|
||||||
* @param[out] namespace
|
* @param[out] namespace
|
||||||
* @param[out] address
|
* @param[out] address Address as string, eg "0.0.0.0", "::"
|
||||||
|
* @param[out] addrtype One of inet:ipv4-address or inet:ipv6-address
|
||||||
* @param[out] port
|
* @param[out] port
|
||||||
* @param[out] ssl
|
* @param[out] ssl
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
cx_evhtp_socket(clicon_handle h,
|
cx_evhtp_socket_extract(clicon_handle h,
|
||||||
cxobj *xs,
|
cxobj *xs,
|
||||||
cvec *nsc,
|
cvec *nsc,
|
||||||
char **namespace,
|
char **namespace,
|
||||||
char **address,
|
char **address,
|
||||||
|
char **addrtype,
|
||||||
uint16_t *port,
|
uint16_t *port,
|
||||||
uint16_t *ssl)
|
uint16_t *ssl)
|
||||||
{
|
{
|
||||||
|
|
@ -726,6 +816,10 @@ cx_evhtp_socket(clicon_handle h,
|
||||||
char *str = NULL;
|
char *str = NULL;
|
||||||
char *reason = NULL;
|
char *reason = NULL;
|
||||||
int ret;
|
int ret;
|
||||||
|
char *body;
|
||||||
|
cg_var *cv = NULL;
|
||||||
|
yang_stmt *y;
|
||||||
|
yang_stmt *ysub = NULL;
|
||||||
|
|
||||||
if ((x = xpath_first(xs, nsc, "namespace")) == NULL){
|
if ((x = xpath_first(xs, nsc, "namespace")) == NULL){
|
||||||
clicon_err(OE_XML, EINVAL, "Mandatory namespace not given");
|
clicon_err(OE_XML, EINVAL, "Mandatory namespace not given");
|
||||||
|
|
@ -736,7 +830,35 @@ cx_evhtp_socket(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "Mandatory address not given");
|
clicon_err(OE_XML, EINVAL, "Mandatory address not given");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
*address = xml_body(x);
|
/* address is a union type and needs a special investigation to see which type (ipv4 or ipv6)
|
||||||
|
* the address is
|
||||||
|
*/
|
||||||
|
body = xml_body(x);
|
||||||
|
y = xml_spec(x);
|
||||||
|
if ((cv = cv_dup(yang_cv_get(y))) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "cv_dup");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if ((ret = cv_parse1(body, cv, &reason)) < 0){
|
||||||
|
clicon_err(OE_XML, errno, "cv_parse1");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (ret == 0){
|
||||||
|
clicon_err(OE_XML, EFAULT, "%s", reason);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if ((ret = ys_cv_validate(h, cv, y, &ysub, &reason)) < 0)
|
||||||
|
goto done;
|
||||||
|
if (ret == 0){
|
||||||
|
clicon_err(OE_XML, EFAULT, "Validation os address: %s", reason);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (ysub == NULL){
|
||||||
|
clicon_err(OE_XML, EFAULT, "No address union type");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
*address = body;
|
||||||
|
*addrtype = yang_argument_get(ysub);
|
||||||
if ((x = xpath_first(xs, nsc, "port")) != NULL &&
|
if ((x = xpath_first(xs, nsc, "port")) != NULL &&
|
||||||
(str = xml_body(x)) != NULL){
|
(str = xml_body(x)) != NULL){
|
||||||
if ((ret = parse_uint16(str, port, &reason)) < 0){
|
if ((ret = parse_uint16(str, port, &reason)) < 0){
|
||||||
|
|
@ -767,7 +889,110 @@ cx_evhtp_socket(clicon_handle h,
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Phase 2 of evhtp init, config has been retrieved from backend
|
static int
|
||||||
|
cx_htp_add(cx_evhtp_handle *eh,
|
||||||
|
evhtp_t *htp)
|
||||||
|
{
|
||||||
|
eh->eh_htplen++;
|
||||||
|
if ((eh->eh_htpvec = realloc(eh->eh_htpvec, eh->eh_htplen*sizeof(htp))) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "realloc");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
eh->eh_htpvec[eh->eh_htplen-1] = htp;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Phase 2 of backend evhtp init, config single socket
|
||||||
|
* @param[in] h Clicon handle
|
||||||
|
* @param[in] eh Evhtp handle
|
||||||
|
* @param[in] ssl_enable Server is SSL enabled
|
||||||
|
* @param[in] xs XML config of single restconf socket
|
||||||
|
* @param[in] nsc Namespace context
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
cx_evhtp_socket(clicon_handle h,
|
||||||
|
cx_evhtp_handle *eh,
|
||||||
|
int ssl_enable,
|
||||||
|
cxobj *xs,
|
||||||
|
cvec *nsc,
|
||||||
|
char *server_cert_path,
|
||||||
|
char *server_key_path,
|
||||||
|
char *server_ca_cert_path,
|
||||||
|
int auth_type_client_certificate)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
char *namespace = NULL;
|
||||||
|
char *address = NULL;
|
||||||
|
char *addrtype = NULL;
|
||||||
|
uint16_t ssl = 0;
|
||||||
|
uint16_t port = 0;
|
||||||
|
int ss;
|
||||||
|
evhtp_t *htp = NULL;
|
||||||
|
|
||||||
|
/* This is socket create a new evhtp_t instance */
|
||||||
|
if ((htp = evhtp_new(eh->eh_evbase, NULL)) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "evhtp_new");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef EVHTP_DISABLE_EVTHR /* threads */
|
||||||
|
evhtp_use_threads_wexit(htp, NULL, NULL, 4, NULL);
|
||||||
|
#endif
|
||||||
|
/* Callback before the connection is accepted. */
|
||||||
|
evhtp_set_pre_accept_cb(htp, cx_pre_accept, h);
|
||||||
|
/* Callback right after a connection is accepted. */
|
||||||
|
evhtp_set_post_accept_cb(htp, cx_post_accept, h);
|
||||||
|
/* Callback to be executed for all /restconf api calls */
|
||||||
|
if (evhtp_set_cb(htp, "/" RESTCONF_API, cx_path_restconf, h) == NULL){
|
||||||
|
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* Callback to be executed for all /restconf api calls */
|
||||||
|
if (evhtp_set_cb(htp, RESTCONF_WELL_KNOWN, cx_path_wellknown, h) == NULL){
|
||||||
|
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* Generic callback called if no other callbacks are matched */
|
||||||
|
evhtp_set_gencb(htp, cx_gencb, h);
|
||||||
|
|
||||||
|
/* Extract socket parameters from single socket config: ns, addr, port, ssl */
|
||||||
|
if (cx_evhtp_socket_extract(h, xs, nsc, &namespace, &address, &addrtype, &port, &ssl) < 0)
|
||||||
|
goto done;
|
||||||
|
/* Sanity checks of socket parameters */
|
||||||
|
if (ssl){
|
||||||
|
if (ssl_enable == 0 || server_cert_path==NULL || server_key_path == NULL){
|
||||||
|
clicon_err(OE_XML, EINVAL, "Enabled SSL server requires server_cert_path and server_key_path");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
// ssl_verify_mode = htp_sslutil_verify2opts(optarg);
|
||||||
|
if (evhtp_ssl_init(htp, eh->eh_ssl_config) < 0){
|
||||||
|
clicon_err(OE_UNIX, errno, "evhtp_new");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Open restconf socket and bind */
|
||||||
|
if (restconf_socket_init(h, address, addrtype, port, &ss) < 0)
|
||||||
|
goto done;
|
||||||
|
/* ss is a server socket that the clients connect to. The callback
|
||||||
|
therefore accepts clients on ss */
|
||||||
|
/* XXX address in evhtp should be prefixed with eg "ipv4:" */
|
||||||
|
evutil_make_socket_closeonexec(ss); // XXX
|
||||||
|
evutil_make_socket_nonblocking(ss); // XXX
|
||||||
|
if (evhtp_accept_socket(htp, ss, SOCKET_LISTEN_BACKLOG) < 0) {
|
||||||
|
/* accept_socket() does not close the descriptor
|
||||||
|
* on error, but this function does.
|
||||||
|
*/
|
||||||
|
evutil_closesocket(ss);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (cx_htp_add(eh, htp) < 0)
|
||||||
|
goto done;
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Phase 2 of backend evhtp init, config has been retrieved from backend
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] xconfig XML config
|
* @param[in] xconfig XML config
|
||||||
* @param[in] nsc Namespace context
|
* @param[in] nsc Namespace context
|
||||||
|
|
@ -780,23 +1005,20 @@ cx_evhtp_init(clicon_handle h,
|
||||||
cxobj *xconfig,
|
cxobj *xconfig,
|
||||||
cvec *nsc,
|
cvec *nsc,
|
||||||
cx_evhtp_handle *eh)
|
cx_evhtp_handle *eh)
|
||||||
|
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
int auth_type_client_certificate = 0;
|
|
||||||
uint16_t port = 0;
|
|
||||||
cxobj *xrestconf;
|
cxobj *xrestconf;
|
||||||
cxobj **vec = NULL;
|
cxobj **vec = NULL;
|
||||||
size_t veclen;
|
size_t veclen;
|
||||||
char *auth_type = NULL;
|
|
||||||
char *server_cert_path = NULL;
|
char *server_cert_path = NULL;
|
||||||
char *server_key_path = NULL;
|
char *server_key_path = NULL;
|
||||||
char *server_ca_cert_path = NULL;
|
char *server_ca_cert_path = NULL;
|
||||||
|
char *auth_type = NULL;
|
||||||
|
int auth_type_client_certificate = 0;
|
||||||
//XXX char *client_cert_ca = NULL;
|
//XXX char *client_cert_ca = NULL;
|
||||||
cxobj *x;
|
cxobj *x;
|
||||||
char *namespace = NULL;
|
int i;
|
||||||
char *address = NULL;
|
int ssl_enable = 0;
|
||||||
uint16_t use_ssl_server = 0;
|
|
||||||
|
|
||||||
/* Extract socket fields from xconfig */
|
/* Extract socket fields from xconfig */
|
||||||
if ((xrestconf = xpath_first(xconfig, nsc, "restconf")) == NULL){
|
if ((xrestconf = xpath_first(xconfig, nsc, "restconf")) == NULL){
|
||||||
|
|
@ -804,6 +1026,8 @@ cx_evhtp_init(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
/* get common fields */
|
/* get common fields */
|
||||||
|
if ((x = xpath_first(xrestconf, nsc, "ssl-enable")) != NULL)
|
||||||
|
ssl_enable = (strcmp(xml_body(x),"true")==0);
|
||||||
if ((x = xpath_first(xrestconf, nsc, "auth-type")) != NULL) /* XXX: leaf-list? */
|
if ((x = xpath_first(xrestconf, nsc, "auth-type")) != NULL) /* XXX: leaf-list? */
|
||||||
auth_type = xml_body(x);
|
auth_type = xml_body(x);
|
||||||
if (auth_type && strcmp(auth_type, "client-certificate") == 0)
|
if (auth_type && strcmp(auth_type, "client-certificate") == 0)
|
||||||
|
|
@ -814,42 +1038,9 @@ cx_evhtp_init(clicon_handle h,
|
||||||
server_key_path = xml_body(x);
|
server_key_path = xml_body(x);
|
||||||
if ((x = xpath_first(xrestconf, nsc, "server-ca-cert-path")) != NULL)
|
if ((x = xpath_first(xrestconf, nsc, "server-ca-cert-path")) != NULL)
|
||||||
server_ca_cert_path = xml_body(x);
|
server_ca_cert_path = xml_body(x);
|
||||||
/* get the list of socket config-data */
|
|
||||||
if (xpath_vec(xrestconf, nsc, "socket", &vec, &veclen) < 0)
|
/* Here the daemon either uses SSL or not, ie you cant seem to mix http and https :-( */
|
||||||
goto done;
|
if (ssl_enable){
|
||||||
/* Accept only a single socket XXX */
|
|
||||||
if (veclen != 1){
|
|
||||||
clicon_err(OE_XML, EINVAL, "Only single socket supported"); /* XXX warning: accept more? */
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if (cx_evhtp_socket(h, vec[0], nsc, &namespace, &address, &port, &use_ssl_server) < 0)
|
|
||||||
goto done;
|
|
||||||
if (use_ssl_server &&
|
|
||||||
(server_cert_path==NULL || server_key_path == NULL)){
|
|
||||||
clicon_err(OE_XML, EINVAL, "Enabled SSL server requires server_cert_path and server_key_path");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if (auth_type_client_certificate){
|
|
||||||
if (!use_ssl_server){
|
|
||||||
clicon_err(OE_XML, EINVAL, "Client certificate authentication type requires SSL");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if (server_ca_cert_path == NULL){
|
|
||||||
clicon_err(OE_XML, EINVAL, "Client certificate authentication type requires server-ca-cert-path");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Init evhtp */
|
|
||||||
if ((eh->eh_evbase = event_base_new()) == NULL){
|
|
||||||
clicon_err(OE_UNIX, errno, "event_base_new");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
/* create a new evhtp_t instance */
|
|
||||||
if ((eh->eh_htp = evhtp_new(eh->eh_evbase, NULL)) == NULL){
|
|
||||||
clicon_err(OE_UNIX, errno, "evhtp_new");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if (use_ssl_server){
|
|
||||||
/* Init evhtp ssl config struct */
|
/* Init evhtp ssl config struct */
|
||||||
if ((eh->eh_ssl_config = malloc(sizeof(evhtp_ssl_cfg_t))) == NULL){
|
if ((eh->eh_ssl_config = malloc(sizeof(evhtp_ssl_cfg_t))) == NULL){
|
||||||
clicon_err(OE_UNIX, errno, "malloc");
|
clicon_err(OE_UNIX, errno, "malloc");
|
||||||
|
|
@ -858,12 +1049,12 @@ cx_evhtp_init(clicon_handle h,
|
||||||
memset(eh->eh_ssl_config, 0, sizeof(evhtp_ssl_cfg_t));
|
memset(eh->eh_ssl_config, 0, sizeof(evhtp_ssl_cfg_t));
|
||||||
eh->eh_ssl_config->ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1;
|
eh->eh_ssl_config->ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1;
|
||||||
|
|
||||||
if (cx_get_ssl_server_certs(h, server_cert_path,
|
/* Read server ssl files cert and key */
|
||||||
server_key_path,
|
if (cx_get_ssl_server_certs(h, server_cert_path, server_key_path, eh->eh_ssl_config) < 0)
|
||||||
eh->eh_ssl_config) < 0)
|
|
||||||
goto done;
|
goto done;
|
||||||
|
/* If client auth get client CA cert */
|
||||||
if (auth_type_client_certificate)
|
if (auth_type_client_certificate)
|
||||||
if (cx_get_ssl_client_certs(h, server_ca_cert_path, eh->eh_ssl_config) < 0)
|
if (cx_get_ssl_client_ca_certs(h, server_ca_cert_path, eh->eh_ssl_config) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
eh->eh_ssl_config->x509_verify_cb = cx_verify_certs; /* Is extra verification necessary? */
|
eh->eh_ssl_config->x509_verify_cb = cx_verify_certs; /* Is extra verification necessary? */
|
||||||
if (auth_type_client_certificate){
|
if (auth_type_client_certificate){
|
||||||
|
|
@ -873,51 +1064,14 @@ cx_evhtp_init(clicon_handle h,
|
||||||
}
|
}
|
||||||
// ssl_verify_mode = htp_sslutil_verify2opts(optarg);
|
// ssl_verify_mode = htp_sslutil_verify2opts(optarg);
|
||||||
}
|
}
|
||||||
assert(SSL_VERIFY_NONE == 0);
|
|
||||||
|
|
||||||
/* Init evhtp */
|
/* get the list of socket config-data */
|
||||||
if ((eh->eh_evbase = event_base_new()) == NULL){
|
if (xpath_vec(xrestconf, nsc, "socket", &vec, &veclen) < 0)
|
||||||
clicon_err(OE_UNIX, errno, "event_base_new");
|
|
||||||
goto done;
|
goto done;
|
||||||
}
|
for (i=0; i<veclen; i++){
|
||||||
/* create a new evhtp_t instance */
|
if (cx_evhtp_socket(h, eh, ssl_enable, vec[i], nsc,
|
||||||
if ((eh->eh_htp = evhtp_new(eh->eh_evbase, NULL)) == NULL){
|
server_cert_path, server_key_path, server_ca_cert_path,
|
||||||
clicon_err(OE_UNIX, errno, "evhtp_new");
|
auth_type_client_certificate) < 0)
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
/* Here the daemon either uses SSL or not, ie you cant seem to mix http and https :-( */
|
|
||||||
if (use_ssl_server){
|
|
||||||
if (evhtp_ssl_init(eh->eh_htp, eh->eh_ssl_config) < 0){
|
|
||||||
clicon_err(OE_UNIX, errno, "evhtp_new");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#ifndef EVHTP_DISABLE_EVTHR /* threads */
|
|
||||||
evhtp_use_threads_wexit(eh->eh_htp, NULL, NULL, 4, NULL);
|
|
||||||
#endif
|
|
||||||
/* Callback before the connection is accepted. */
|
|
||||||
evhtp_set_pre_accept_cb(eh->eh_htp, cx_pre_accept, h);
|
|
||||||
/* Callback right after a connection is accepted. */
|
|
||||||
evhtp_set_post_accept_cb(eh->eh_htp, cx_post_accept, h);
|
|
||||||
/* Callback to be executed for all /restconf api calls */
|
|
||||||
if (evhtp_set_cb(eh->eh_htp, "/" RESTCONF_API, cx_path_restconf, h) == NULL){
|
|
||||||
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
/* Callback to be executed for all /restconf api calls */
|
|
||||||
if (evhtp_set_cb(eh->eh_htp, RESTCONF_WELL_KNOWN, cx_path_wellknown, h) == NULL){
|
|
||||||
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
/* Generic callback called if no other callbacks are matched */
|
|
||||||
evhtp_set_gencb(eh->eh_htp, cx_gencb, h);
|
|
||||||
|
|
||||||
if (evhtp_bind_socket(eh->eh_htp, /* evhtp handle */
|
|
||||||
address, /* string address, eg ipv4:<ipv4addr> */
|
|
||||||
port, /* port */
|
|
||||||
SOCKET_LISTEN_BACKLOG /* backlog flag, see listen(5) */
|
|
||||||
) < 0){
|
|
||||||
clicon_err(OE_UNIX, errno, "evhtp_bind_socket");
|
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
@ -930,6 +1084,7 @@ cx_evhtp_init(clicon_handle h,
|
||||||
/*! Read config from backend */
|
/*! Read config from backend */
|
||||||
int
|
int
|
||||||
restconf_config_backend(clicon_handle h,
|
restconf_config_backend(clicon_handle h,
|
||||||
|
cx_evhtp_handle *eh,
|
||||||
int argc,
|
int argc,
|
||||||
char **argv,
|
char **argv,
|
||||||
int drop_privileges)
|
int drop_privileges)
|
||||||
|
|
@ -945,8 +1100,8 @@ restconf_config_backend(clicon_handle h,
|
||||||
size_t cligen_bufthreshold;
|
size_t cligen_bufthreshold;
|
||||||
cvec *nsc = NULL;
|
cvec *nsc = NULL;
|
||||||
cxobj *xconfig = NULL;
|
cxobj *xconfig = NULL;
|
||||||
|
cxobj *xerr = NULL;
|
||||||
uint32_t id = 0; /* Session id, to poll backend up */
|
uint32_t id = 0; /* Session id, to poll backend up */
|
||||||
cx_evhtp_handle *eh = NULL;
|
|
||||||
|
|
||||||
/* Set default namespace according to CLICON_NAMESPACE_NETCONF_DEFAULT */
|
/* Set default namespace according to CLICON_NAMESPACE_NETCONF_DEFAULT */
|
||||||
xml_nsctx_namespace_netconf_default(h);
|
xml_nsctx_namespace_netconf_default(h);
|
||||||
|
|
@ -1052,13 +1207,16 @@ restconf_config_backend(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
if (clicon_rpc_get_config(h, NULL, "running", "/restconf", nsc, &xconfig) < 0)
|
if (clicon_rpc_get_config(h, NULL, "running", "/restconf", nsc, &xconfig) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
/* Initialize evhtp handle - fill it in cx_evhtp_init */
|
if ((xerr = xpath_first(xconfig, NULL, "/rpc-error")) != NULL){
|
||||||
if ((eh = malloc(sizeof *eh)) == NULL){
|
clixon_netconf_error(xerr, "Get backend restconf config", NULL);
|
||||||
clicon_err(OE_UNIX, errno, "malloc");
|
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
memset(eh, 0, sizeof *eh);
|
/* Init evhtp, common stuff */
|
||||||
_EVHTP_HANDLE = eh; /* global */
|
if ((eh->eh_evbase = event_base_new()) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "event_base_new");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
if (cx_evhtp_init(h, xconfig, nsc, eh) < 0)
|
if (cx_evhtp_init(h, xconfig, nsc, eh) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
/* Drop privileges after evhtp and server key/cert read */
|
/* Drop privileges after evhtp and server key/cert read */
|
||||||
|
|
@ -1069,7 +1227,6 @@ restconf_config_backend(clicon_handle h,
|
||||||
}
|
}
|
||||||
/* libevent main loop */
|
/* libevent main loop */
|
||||||
event_base_loop(eh->eh_evbase, 0); /* XXX: replace with clixon_event_loop() */
|
event_base_loop(eh->eh_evbase, 0); /* XXX: replace with clixon_event_loop() */
|
||||||
|
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
if (xconfig)
|
if (xconfig)
|
||||||
|
|
@ -1083,6 +1240,7 @@ restconf_config_backend(clicon_handle h,
|
||||||
/*! Read config locally */
|
/*! Read config locally */
|
||||||
int
|
int
|
||||||
restconf_config_local(clicon_handle h,
|
restconf_config_local(clicon_handle h,
|
||||||
|
cx_evhtp_handle *eh,
|
||||||
int argc,
|
int argc,
|
||||||
char **argv,
|
char **argv,
|
||||||
uint16_t port,
|
uint16_t port,
|
||||||
|
|
@ -1101,7 +1259,7 @@ restconf_config_local(clicon_handle h,
|
||||||
size_t cligen_bufthreshold;
|
size_t cligen_bufthreshold;
|
||||||
char *restconf_ipv4_addr = NULL;
|
char *restconf_ipv4_addr = NULL;
|
||||||
char *restconf_ipv6_addr = NULL;
|
char *restconf_ipv6_addr = NULL;
|
||||||
cx_evhtp_handle *eh = NULL;
|
evhtp_t *htp;
|
||||||
|
|
||||||
/* port = defaultport unless explicitly set -P */
|
/* port = defaultport unless explicitly set -P */
|
||||||
if (port == 0){
|
if (port == 0){
|
||||||
|
|
@ -1111,13 +1269,6 @@ restconf_config_local(clicon_handle h,
|
||||||
/* Set default namespace according to CLICON_NAMESPACE_NETCONF_DEFAULT */
|
/* Set default namespace according to CLICON_NAMESPACE_NETCONF_DEFAULT */
|
||||||
xml_nsctx_namespace_netconf_default(h);
|
xml_nsctx_namespace_netconf_default(h);
|
||||||
|
|
||||||
/* Initialize evhtp handle */
|
|
||||||
if ((eh = malloc(sizeof *eh)) == NULL){
|
|
||||||
clicon_err(OE_UNIX, errno, "malloc");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
memset(eh, 0, sizeof *eh);
|
|
||||||
_EVHTP_HANDLE = eh; /* global */
|
|
||||||
/* Check server ssl certs */
|
/* Check server ssl certs */
|
||||||
if (use_ssl){
|
if (use_ssl){
|
||||||
/* Init evhtp ssl config struct */
|
/* Init evhtp ssl config struct */
|
||||||
|
|
@ -1148,39 +1299,6 @@ restconf_config_local(clicon_handle h,
|
||||||
clicon_err(OE_UNIX, errno, "event_base_new");
|
clicon_err(OE_UNIX, errno, "event_base_new");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
/* create a new evhtp_t instance */
|
|
||||||
if ((eh->eh_htp = evhtp_new(eh->eh_evbase, NULL)) == NULL){
|
|
||||||
clicon_err(OE_UNIX, errno, "evhtp_new");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
/* Here the daemon either uses SSL or not, ie you cant seem to mix http and https :-( */
|
|
||||||
if (use_ssl){
|
|
||||||
if (evhtp_ssl_init(eh->eh_htp, eh->eh_ssl_config) < 0){
|
|
||||||
clicon_err(OE_UNIX, errno, "evhtp_new");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#ifndef EVHTP_DISABLE_EVTHR
|
|
||||||
evhtp_use_threads_wexit(eh->eh_htp, NULL, NULL, 4, NULL);
|
|
||||||
#endif
|
|
||||||
/* Callback before the connection is accepted. */
|
|
||||||
evhtp_set_pre_accept_cb(eh->eh_htp, cx_pre_accept, h);
|
|
||||||
|
|
||||||
/* Callback right after a connection is accepted. */
|
|
||||||
evhtp_set_post_accept_cb(eh->eh_htp, cx_post_accept, h);
|
|
||||||
|
|
||||||
/* Callback to be executed for all /restconf api calls */
|
|
||||||
if (evhtp_set_cb(eh->eh_htp, "/" RESTCONF_API, cx_path_restconf, h) == NULL){
|
|
||||||
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
/* Callback to be executed for all /restconf api calls */
|
|
||||||
if (evhtp_set_cb(eh->eh_htp, RESTCONF_WELL_KNOWN, cx_path_wellknown, h) == NULL){
|
|
||||||
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
/* Generic callback called if no other callbacks are matched */
|
|
||||||
evhtp_set_gencb(eh->eh_htp, cx_gencb, h);
|
|
||||||
|
|
||||||
/* bind to a socket, optionally with specific protocol support formatting
|
/* bind to a socket, optionally with specific protocol support formatting
|
||||||
*/
|
*/
|
||||||
|
|
@ -1193,12 +1311,47 @@ restconf_config_local(clicon_handle h,
|
||||||
}
|
}
|
||||||
if (restconf_ipv4_addr != NULL && strlen(restconf_ipv4_addr)){
|
if (restconf_ipv4_addr != NULL && strlen(restconf_ipv4_addr)){
|
||||||
cbuf *cb;
|
cbuf *cb;
|
||||||
|
|
||||||
|
/* create a new evhtp_t instance */
|
||||||
|
if ((htp = evhtp_new(eh->eh_evbase, NULL)) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "evhtp_new");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* Here the daemon either uses SSL or not, ie you cant seem to mix http and https :-( */
|
||||||
|
if (use_ssl){
|
||||||
|
if (evhtp_ssl_init(htp, eh->eh_ssl_config) < 0){
|
||||||
|
clicon_err(OE_UNIX, errno, "evhtp_new");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifndef EVHTP_DISABLE_EVTHR
|
||||||
|
evhtp_use_threads_wexit(htp, NULL, NULL, 4, NULL);
|
||||||
|
#endif
|
||||||
|
/* Callback before the connection is accepted. */
|
||||||
|
evhtp_set_pre_accept_cb(htp, cx_pre_accept, h);
|
||||||
|
|
||||||
|
/* Callback right after a connection is accepted. */
|
||||||
|
evhtp_set_post_accept_cb(htp, cx_post_accept, h);
|
||||||
|
|
||||||
|
/* Callback to be executed for all /restconf api calls */
|
||||||
|
if (evhtp_set_cb(htp, "/" RESTCONF_API, cx_path_restconf, h) == NULL){
|
||||||
|
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* Callback to be executed for all /restconf api calls */
|
||||||
|
if (evhtp_set_cb(htp, RESTCONF_WELL_KNOWN, cx_path_wellknown, h) == NULL){
|
||||||
|
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* Generic callback called if no other callbacks are matched */
|
||||||
|
evhtp_set_gencb(htp, cx_gencb, h);
|
||||||
|
|
||||||
if ((cb = cbuf_new()) == NULL){
|
if ((cb = cbuf_new()) == NULL){
|
||||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
cprintf(cb, "ipv4:%s", restconf_ipv4_addr);
|
cprintf(cb, "ipv4:%s", restconf_ipv4_addr);
|
||||||
if (evhtp_bind_socket(eh->eh_htp, /* evhtp handle */
|
if (evhtp_bind_socket(htp, /* evhtp handle */
|
||||||
cbuf_get(cb), /* string address, eg ipv4:<ipv4addr> */
|
cbuf_get(cb), /* string address, eg ipv4:<ipv4addr> */
|
||||||
port, /* port */
|
port, /* port */
|
||||||
SOCKET_LISTEN_BACKLOG /* backlog flag, see listen(5) */
|
SOCKET_LISTEN_BACKLOG /* backlog flag, see listen(5) */
|
||||||
|
|
@ -1208,16 +1361,51 @@ restconf_config_local(clicon_handle h,
|
||||||
}
|
}
|
||||||
if (cb)
|
if (cb)
|
||||||
cbuf_free(cb);
|
cbuf_free(cb);
|
||||||
|
if (cx_htp_add(eh, htp) < 0)
|
||||||
|
goto done;
|
||||||
}
|
}
|
||||||
/* Eeh can only bind one */
|
/* Eeh can only bind one */
|
||||||
if (0 && restconf_ipv6_addr != NULL && strlen(restconf_ipv6_addr)){
|
if (restconf_ipv6_addr != NULL && strlen(restconf_ipv6_addr)){
|
||||||
cbuf *cb;
|
cbuf *cb;
|
||||||
|
/* create a new evhtp_t instance */
|
||||||
|
if ((htp = evhtp_new(eh->eh_evbase, NULL)) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "evhtp_new");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* Here the daemon either uses SSL or not, ie you cant seem to mix http and https :-( */
|
||||||
|
if (use_ssl){
|
||||||
|
if (evhtp_ssl_init(htp, eh->eh_ssl_config) < 0){
|
||||||
|
clicon_err(OE_UNIX, errno, "evhtp_new");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifndef EVHTP_DISABLE_EVTHR
|
||||||
|
evhtp_use_threads_wexit(htp, NULL, NULL, 4, NULL);
|
||||||
|
#endif
|
||||||
|
/* Callback before the connection is accepted. */
|
||||||
|
evhtp_set_pre_accept_cb(htp, cx_pre_accept, h);
|
||||||
|
|
||||||
|
/* Callback right after a connection is accepted. */
|
||||||
|
evhtp_set_post_accept_cb(htp, cx_post_accept, h);
|
||||||
|
|
||||||
|
/* Callback to be executed for all /restconf api calls */
|
||||||
|
if (evhtp_set_cb(htp, "/" RESTCONF_API, cx_path_restconf, h) == NULL){
|
||||||
|
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* Callback to be executed for all /restconf api calls */
|
||||||
|
if (evhtp_set_cb(htp, RESTCONF_WELL_KNOWN, cx_path_wellknown, h) == NULL){
|
||||||
|
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* Generic callback called if no other callbacks are matched */
|
||||||
|
evhtp_set_gencb(htp, cx_gencb, h);
|
||||||
if ((cb = cbuf_new()) == NULL){
|
if ((cb = cbuf_new()) == NULL){
|
||||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
cprintf(cb, "ipv6:%s", restconf_ipv6_addr);
|
cprintf(cb, "ipv6:%s", restconf_ipv6_addr);
|
||||||
if (evhtp_bind_socket(eh->eh_htp, /* evhtp handle */
|
if (evhtp_bind_socket(htp, /* evhtp handle */
|
||||||
cbuf_get(cb), /* string address, eg ipv6:<ipv6addr> */
|
cbuf_get(cb), /* string address, eg ipv6:<ipv6addr> */
|
||||||
port, /* port */
|
port, /* port */
|
||||||
SOCKET_LISTEN_BACKLOG /* backlog flag, see listen(5) */
|
SOCKET_LISTEN_BACKLOG /* backlog flag, see listen(5) */
|
||||||
|
|
@ -1227,6 +1415,8 @@ restconf_config_local(clicon_handle h,
|
||||||
}
|
}
|
||||||
if (cb)
|
if (cb)
|
||||||
cbuf_free(cb);
|
cbuf_free(cb);
|
||||||
|
if (cx_htp_add(eh, htp) < 0)
|
||||||
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (drop_privileges){
|
if (drop_privileges){
|
||||||
|
|
@ -1491,10 +1681,16 @@ main(int argc,
|
||||||
/* port = defaultport unless explicitly set -P */
|
/* port = defaultport unless explicitly set -P */
|
||||||
if (port == 0)
|
if (port == 0)
|
||||||
port = defaultport;
|
port = defaultport;
|
||||||
|
if ((eh = malloc(sizeof *eh)) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "malloc");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
memset(eh, 0, sizeof *eh);
|
||||||
|
_EVHTP_HANDLE = eh; /* global */
|
||||||
|
|
||||||
if (clicon_option_bool(h, "CLICON_RESTCONF_CONFIG") == 0){
|
if (clicon_option_bool(h, "CLICON_RESTCONF_CONFIG") == 0){
|
||||||
/* Read config locally */
|
/* Read config locally */
|
||||||
if (restconf_config_local(h, argc, argv,
|
if (restconf_config_local(h, eh, argc, argv,
|
||||||
port,
|
port,
|
||||||
ssl_verify_clients,
|
ssl_verify_clients,
|
||||||
use_ssl,
|
use_ssl,
|
||||||
|
|
@ -1504,12 +1700,10 @@ main(int argc,
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
/* Read config from backend */
|
/* Read config from backend */
|
||||||
if (restconf_config_backend(h, argc, argv, drop_privileges) < 0)
|
if (restconf_config_backend(h, eh, argc, argv, drop_privileges) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
event_base_loop(eh->eh_evbase, 0);
|
|
||||||
|
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
clicon_debug(1, "restconf_main_evhtp done");
|
clicon_debug(1, "restconf_main_evhtp done");
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,10 @@
|
||||||
#
|
#
|
||||||
# ***** END LICENSE BLOCK *****
|
# ***** END LICENSE BLOCK *****
|
||||||
#
|
#
|
||||||
|
# Build, compile and test a clixon-system container
|
||||||
|
# This is used in CI
|
||||||
|
# NOTE: restconf config is controlled at install time with ./configure --with-restconf=... option
|
||||||
|
|
||||||
VPATH = @srcdir@
|
VPATH = @srcdir@
|
||||||
srcdir = @srcdir@
|
srcdir = @srcdir@
|
||||||
top_srcdir = @top_srcdir@
|
top_srcdir = @top_srcdir@
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ int yang2cv_type(char *ytype, enum cv_type *cv_type);
|
||||||
char *cv2yang_type(enum cv_type cv_type);
|
char *cv2yang_type(enum cv_type cv_type);
|
||||||
yang_stmt *yang_find_identity(yang_stmt *ys, char *identity);
|
yang_stmt *yang_find_identity(yang_stmt *ys, char *identity);
|
||||||
yang_stmt *yang_find_identity_nsc(yang_stmt *yspec, char *identity, cvec *nsc);
|
yang_stmt *yang_find_identity_nsc(yang_stmt *yspec, char *identity, cvec *nsc);
|
||||||
int ys_cv_validate(clicon_handle h, cg_var *cv, yang_stmt *ys, char **reason);
|
int ys_cv_validate(clicon_handle h, cg_var *cv, yang_stmt *ys, yang_stmt **ysub, char **reason);
|
||||||
int clicon_type2cv(char *type, char *rtype, yang_stmt *ys, enum cv_type *cvtype);
|
int clicon_type2cv(char *type, char *rtype, yang_stmt *ys, enum cv_type *cvtype);
|
||||||
int yang_type_get(yang_stmt *ys, char **otype, yang_stmt **restype,
|
int yang_type_get(yang_stmt *ys, char **otype, yang_stmt **restype,
|
||||||
int *options, cvec **cvv,
|
int *options, cvec **cvv,
|
||||||
|
|
|
||||||
|
|
@ -1024,7 +1024,7 @@ xml_yang_validate_add(clicon_handle h,
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((ret = ys_cv_validate(h, cv, yt, &reason)) < 0)
|
if ((ret = ys_cv_validate(h, cv, yt, NULL, &reason)) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (ret == 0){
|
if (ret == 0){
|
||||||
if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0)
|
if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0)
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,7 @@ compile_pattern2regexp(clicon_handle h,
|
||||||
/*! Resolve types: populate type caches
|
/*! Resolve types: populate type caches
|
||||||
* @param[in] ys This is a type statement
|
* @param[in] ys This is a type statement
|
||||||
* @param[in] arg Not used
|
* @param[in] arg Not used
|
||||||
* Typically only called once when loading te yang type system.
|
* Typically only called once when loading the yang type system.
|
||||||
* @note unions not cached
|
* @note unions not cached
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
|
|
@ -741,7 +741,7 @@ cv_validate1(clicon_handle h,
|
||||||
|
|
||||||
/* Forward */
|
/* Forward */
|
||||||
static int ys_cv_validate_union(clicon_handle h,yang_stmt *ys, char **reason,
|
static int ys_cv_validate_union(clicon_handle h,yang_stmt *ys, char **reason,
|
||||||
yang_stmt *yrestype, char *type, char *val);
|
yang_stmt *yrestype, char *type, char *val, yang_stmt **ysubp);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @param[out] reason If given and return val is 0, contains a malloced string
|
* @param[out] reason If given and return val is 0, contains a malloced string
|
||||||
|
|
@ -767,6 +767,7 @@ ys_cv_validate_union_one(clicon_handle h,
|
||||||
char *restype;
|
char *restype;
|
||||||
enum cv_type cvtype;
|
enum cv_type cvtype;
|
||||||
cg_var *cvt=NULL;
|
cg_var *cvt=NULL;
|
||||||
|
yang_stmt *ysubt = NULL;
|
||||||
|
|
||||||
if ((regexps = cvec_new(0)) == NULL){
|
if ((regexps = cvec_new(0)) == NULL){
|
||||||
clicon_err(OE_UNIX, errno, "cvec_new");
|
clicon_err(OE_UNIX, errno, "cvec_new");
|
||||||
|
|
@ -781,7 +782,7 @@ ys_cv_validate_union_one(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
restype = yrt?yang_argument_get(yrt):NULL;
|
restype = yrt?yang_argument_get(yrt):NULL;
|
||||||
if (restype && strcmp(restype, "union") == 0){ /* recursive union */
|
if (restype && strcmp(restype, "union") == 0){ /* recursive union */
|
||||||
if ((retval = ys_cv_validate_union(h, ys, reason, yrt, type, val)) < 0)
|
if ((retval = ys_cv_validate_union(h, ys, reason, yrt, type, val, &ysubt)) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -828,7 +829,11 @@ ys_cv_validate_union_one(clicon_handle h,
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Validate union
|
/*! Validate union
|
||||||
|
* @param[in] h Clixon handle
|
||||||
|
* @param[in] ys Yang statement (union)
|
||||||
* @param[out] reason If given, and return value is 0, contains malloced string
|
* @param[out] reason If given, and return value is 0, contains malloced string
|
||||||
|
* @param[in] val Value to match
|
||||||
|
* @param[out] ysubp Sub-type of ys that matches val
|
||||||
* @retval -1 Error (fatal), with errno set to indicate error
|
* @retval -1 Error (fatal), with errno set to indicate error
|
||||||
* @retval 0 Validation not OK, malloced reason is returned. Free reason with free()
|
* @retval 0 Validation not OK, malloced reason is returned. Free reason with free()
|
||||||
* @retval 1 Validation OK
|
* @retval 1 Validation OK
|
||||||
|
|
@ -839,7 +844,8 @@ ys_cv_validate_union(clicon_handle h,
|
||||||
char **reason,
|
char **reason,
|
||||||
yang_stmt *yrestype,
|
yang_stmt *yrestype,
|
||||||
char *type, /* orig type */
|
char *type, /* orig type */
|
||||||
char *val)
|
char *val,
|
||||||
|
yang_stmt **ysubp)
|
||||||
{
|
{
|
||||||
int retval = 1; /* valid */
|
int retval = 1; /* valid */
|
||||||
yang_stmt *yt = NULL;
|
yang_stmt *yt = NULL;
|
||||||
|
|
@ -859,9 +865,14 @@ ys_cv_validate_union(clicon_handle h,
|
||||||
reason1 = *reason;
|
reason1 = *reason;
|
||||||
*reason = NULL;
|
*reason = NULL;
|
||||||
}
|
}
|
||||||
if (retval == 1) /* Enough that one type validates value */
|
/* Enough that one type validates value, return that value
|
||||||
|
*/
|
||||||
|
if (retval == 1) {
|
||||||
|
if (ysubp)
|
||||||
|
*ysubp = yt;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
done:
|
done:
|
||||||
if (retval == 0 && reason1){
|
if (retval == 0 && reason1){
|
||||||
*reason = reason1;
|
*reason = reason1;
|
||||||
|
|
@ -877,6 +888,7 @@ ys_cv_validate_union(clicon_handle h,
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] cv A cligen variable to validate. This is a correctly parsed cv.
|
* @param[in] cv A cligen variable to validate. This is a correctly parsed cv.
|
||||||
* @param[in] ys A yang statement, must be leaf or leaf-list.
|
* @param[in] ys A yang statement, must be leaf or leaf-list.
|
||||||
|
* @param[out] ysub Sub-type that matches val (in case of union, otherwise ys)
|
||||||
* @param[out] reason If given, and if return value is 0, contains malloced
|
* @param[out] reason If given, and if return value is 0, contains malloced
|
||||||
* string describing reason why validation failed.
|
* string describing reason why validation failed.
|
||||||
* @retval -1 Error (fatal), with errno set to indicate error
|
* @retval -1 Error (fatal), with errno set to indicate error
|
||||||
|
|
@ -889,6 +901,7 @@ int
|
||||||
ys_cv_validate(clicon_handle h,
|
ys_cv_validate(clicon_handle h,
|
||||||
cg_var *cv,
|
cg_var *cv,
|
||||||
yang_stmt *ys,
|
yang_stmt *ys,
|
||||||
|
yang_stmt **ysub,
|
||||||
char **reason)
|
char **reason)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
|
|
@ -948,7 +961,7 @@ ys_cv_validate(clicon_handle h,
|
||||||
*/
|
*/
|
||||||
if ((val = cv_string_get(cv)) == NULL)
|
if ((val = cv_string_get(cv)) == NULL)
|
||||||
val = "";
|
val = "";
|
||||||
if ((retval2 = ys_cv_validate_union(h, ys, reason, yrestype, origtype, val)) < 0)
|
if ((retval2 = ys_cv_validate_union(h, ys, reason, yrestype, origtype, val, ysub)) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
retval = retval2; /* invalid (0) with latest reason or valid 1 */
|
retval = retval2; /* invalid (0) with latest reason or valid 1 */
|
||||||
}
|
}
|
||||||
|
|
@ -969,6 +982,8 @@ ys_cv_validate(clicon_handle h,
|
||||||
if ((retval = cv_validate1(h, cv, cvtype, options, cvv,
|
if ((retval = cv_validate1(h, cv, cvtype, options, cvv,
|
||||||
regexps, yrestype, restype, reason)) < 0)
|
regexps, yrestype, restype, reason)) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
if (ysub)
|
||||||
|
*ysub = ys;
|
||||||
}
|
}
|
||||||
done:
|
done:
|
||||||
if (origtype)
|
if (origtype)
|
||||||
|
|
|
||||||
67
test/certs.sh
Normal file
67
test/certs.sh
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Create server certs
|
||||||
|
# Assume: the following variables set:
|
||||||
|
# $dir, $certdir, $srvkey, $srvcert, $cakey, $cacert
|
||||||
|
# and that $certdir exists
|
||||||
|
|
||||||
|
# 1. CA
|
||||||
|
cat<<EOF > $dir/ca.cnf
|
||||||
|
[ ca ]
|
||||||
|
default_ca = CA_default
|
||||||
|
|
||||||
|
[ CA_default ]
|
||||||
|
serial = ca-serial
|
||||||
|
crl = ca-crl.pem
|
||||||
|
database = ca-database.txt
|
||||||
|
name_opt = CA_default
|
||||||
|
cert_opt = CA_default
|
||||||
|
default_crl_days = 9999
|
||||||
|
default_md = md5
|
||||||
|
|
||||||
|
[ req ]
|
||||||
|
default_bits = 2048
|
||||||
|
days = 1
|
||||||
|
distinguished_name = req_distinguished_name
|
||||||
|
attributes = req_attributes
|
||||||
|
prompt = no
|
||||||
|
output_password = password
|
||||||
|
|
||||||
|
[ req_distinguished_name ]
|
||||||
|
C = SE
|
||||||
|
L = Stockholm
|
||||||
|
O = Clixon
|
||||||
|
OU = clixon
|
||||||
|
CN = ca
|
||||||
|
emailAddress = olof@hagsand.se
|
||||||
|
|
||||||
|
[ req_attributes ]
|
||||||
|
challengePassword = test
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Generate CA cert
|
||||||
|
openssl req -x509 -days 1 -config $dir/ca.cnf -keyout $cakey -out $cacert
|
||||||
|
|
||||||
|
cat<<EOF > $dir/srv.cnf
|
||||||
|
[req]
|
||||||
|
prompt = no
|
||||||
|
distinguished_name = dn
|
||||||
|
req_extensions = ext
|
||||||
|
[dn]
|
||||||
|
CN = www.clicon.org # localhost
|
||||||
|
emailAddress = olof@hagsand.se
|
||||||
|
O = Clixon
|
||||||
|
L = Stockholm
|
||||||
|
C = SE
|
||||||
|
[ext]
|
||||||
|
subjectAltName = DNS:clicon.org
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Generate server key
|
||||||
|
openssl genrsa -out $srvkey 2048
|
||||||
|
|
||||||
|
# Generate CSR (signing request)
|
||||||
|
openssl req -new -config $dir/srv.cnf -key $srvkey -out $certdir/srv_csr.pem
|
||||||
|
|
||||||
|
# Sign server cert by CA
|
||||||
|
openssl x509 -req -extfile $dir/srv.cnf -days 1 -passin "pass:password" -in $certdir/srv_csr.pem -CA $cacert -CAkey $cakey -CAcreateserial -out $srvcert
|
||||||
|
|
@ -42,6 +42,7 @@ if [ -f ./config.sh ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Sanity nginx running on systemd platforms
|
# Sanity nginx running on systemd platforms
|
||||||
|
# ./lib.sh: line 45: systemctl: command not found
|
||||||
if systemctl > /dev/null; then
|
if systemctl > /dev/null; then
|
||||||
nginxactive=$(systemctl show nginx |grep ActiveState=active)
|
nginxactive=$(systemctl show nginx |grep ActiveState=active)
|
||||||
if [ "${WITH_RESTCONF}" = "fcgi" ]; then
|
if [ "${WITH_RESTCONF}" = "fcgi" ]; then
|
||||||
|
|
@ -273,8 +274,8 @@ wait_backend(){
|
||||||
# @see wait_restconf
|
# @see wait_restconf
|
||||||
start_restconf(){
|
start_restconf(){
|
||||||
# Start in background
|
# Start in background
|
||||||
if [ $RCPROTO = https ]; then
|
if [ $RCPROTO = https -a "${WITH_RESTCONF}" = "evhtp" ]; then
|
||||||
EXTRA="-s" # server certs
|
EXTRA="-s" # server certs ONLY evhtp
|
||||||
else
|
else
|
||||||
EXTRA=
|
EXTRA=
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,18 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Restconf basic functionality
|
# Restconf basic functionality also uri encoding using eth/0/0
|
||||||
# also uri encoding using eth/0/0
|
# Note there are many variants: (1)fcgi/evhtp, (2) http/https, (3) IPv4/IPv6, (4)local or backend-config
|
||||||
# Assume http server setup, such as nginx described in apps/restconf/README.md
|
# (1) fcgi/evhtp
|
||||||
|
# This is compile-time --with-restconf=fcgi or evhtp, so either or
|
||||||
|
# - fcgi: Assume http server setup, such as nginx described in apps/restconf/README.md
|
||||||
|
# - evhtp: test both local config and get config from backend
|
||||||
|
# (2) http/https
|
||||||
|
# - fcgi: relies on nginx has https setup
|
||||||
|
# - evhtp: generate self-signed server certs
|
||||||
|
# (3) IPv4/IPv6 (only loopback 127.0.0.1 / ::1)
|
||||||
|
# - The tests runs through both
|
||||||
|
# (4) local/backend config. Evhtp only
|
||||||
|
# - The tests runs through both (if compiled with evhtp)
|
||||||
|
# See also test_restconf2.sh
|
||||||
|
|
||||||
# Magic line must be first in script (see README.md)
|
# Magic line must be first in script (see README.md)
|
||||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||||
|
|
@ -29,23 +40,61 @@ cat <<EOF > $cfg
|
||||||
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
||||||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||||
<CLICON_MODULE_LIBRARY_RFC7895>true</CLICON_MODULE_LIBRARY_RFC7895>
|
<CLICON_MODULE_LIBRARY_RFC7895>true</CLICON_MODULE_LIBRARY_RFC7895>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ "${WITH_RESTCONF}" = "evhtp" ]; then
|
||||||
|
# Create server certs
|
||||||
|
certdir=$dir/certs
|
||||||
|
srvkey=$certdir/srv_key.pem
|
||||||
|
srvcert=$certdir/srv_cert.pem
|
||||||
|
cakey=$certdir/ca_key.pem # needed?
|
||||||
|
cacert=$certdir/ca_cert.pem
|
||||||
|
test -d $certdir || mkdir $certdir
|
||||||
|
. ./certs.sh
|
||||||
|
cat <<EOF >> $cfg
|
||||||
|
<CLICON_SSL_SERVER_CERT>$srvcert</CLICON_SSL_SERVER_CERT>
|
||||||
|
<CLICON_SSL_SERVER_KEY>$srvkey</CLICON_SSL_SERVER_KEY>
|
||||||
|
<CLICON_SSL_CA_CERT>$srvcert</CLICON_SSL_CA_CERT>
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOF >> $cfg
|
||||||
</clixon-config>
|
</clixon-config>
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# This is a fixed 'state' implemented in routing_backend. It is assumed to be always there
|
# This is a fixed 'state' implemented in routing_backend. It is assumed to be always there
|
||||||
state='{"clixon-example:state":{"op":\["41","42","43"\]}'
|
state='{"clixon-example:state":{"op":\["41","42","43"\]}'
|
||||||
|
|
||||||
if [ ${RCPROTO} = "https" ]; then
|
# For backend config, create 4 sockets, all combinations IPv4/IPv6 + http/https
|
||||||
ssl=true
|
RESTCONFCONFIG=$(cat <<EOF
|
||||||
port=443
|
<restconf xmlns="https://clicon.org/restconf">
|
||||||
else
|
<ssl-enable>true</ssl-enable>
|
||||||
ssl=false
|
<auth-type>password</auth-type>
|
||||||
port=80
|
<server-cert-path>$srvcert</server-cert-path>
|
||||||
fi
|
<server-key-path>$srvkey</server-key-path>
|
||||||
|
<server-ca-cert-path>$cakey</server-ca-cert-path>
|
||||||
|
<socket><namespace>default</namespace><address>0.0.0.0</address><port>80</port><ssl>false</ssl></socket>
|
||||||
|
<socket><namespace>default</namespace><address>::</address><port>80</port><ssl>false</ssl></socket>
|
||||||
|
<socket><namespace>default</namespace><address>0.0.0.0</address><port>443</port><ssl>true</ssl></socket>
|
||||||
|
<socket><namespace>default</namespace><address>::</address><port>443</port><ssl>true</ssl></socket>
|
||||||
|
</restconf>
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
# Restconf test routine with arguments:
|
||||||
|
# 1. proto:http/https
|
||||||
|
# 2: addr: 127.0.0.1/::1 # IPv4 or IPv6
|
||||||
|
# 3: config: local / backend config (evhtp only)
|
||||||
testrun()
|
testrun()
|
||||||
{
|
{
|
||||||
USEBACKEND=$1
|
proto=$1 # http/https
|
||||||
|
addr=$2 # 127.0.0.1/::1
|
||||||
|
config=$3 # local/backend
|
||||||
|
|
||||||
|
RCPROTO=$proto # for start/wait of restconf
|
||||||
|
echo "proto:$proto"
|
||||||
|
echo "addr:$addr"
|
||||||
|
echo "config:$config"
|
||||||
|
|
||||||
new "test params: -f $cfg -- -s"
|
new "test params: -f $cfg -- -s"
|
||||||
if [ $BE -ne 0 ]; then
|
if [ $BE -ne 0 ]; then
|
||||||
|
|
@ -63,10 +112,10 @@ testrun()
|
||||||
new "wait backend"
|
new "wait backend"
|
||||||
wait_backend
|
wait_backend
|
||||||
|
|
||||||
if $USEBACKEND; then
|
if [ $config = backend ] ; then # Create a backend config
|
||||||
|
# restconf backend config
|
||||||
new "netconf edit config"
|
new "netconf edit config"
|
||||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><restconf xmlns=\"https://clicon.org/restconf\"><socket><namespace>default</namespace><address>0.0.0.0</address><port>$port</port><ssl>$ssl</ssl></socket><auth-type>password</auth-type></restconf></config></edit-config></rpc>]]>]]>"
|
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc $DEFAULTNS><edit-config><target><candidate/></target><config>$RESTCONFCONFIG</config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
|
||||||
"^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
|
|
||||||
|
|
||||||
new "netconf commit"
|
new "netconf commit"
|
||||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc $DEFAULTNS><commit/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc $DEFAULTNS><commit/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
@ -76,7 +125,7 @@ testrun()
|
||||||
new "kill old restconf daemon"
|
new "kill old restconf daemon"
|
||||||
stop_restconf_pre
|
stop_restconf_pre
|
||||||
|
|
||||||
if $USEBACKEND; then
|
if [ $config = backend ] ; then # Add -b option
|
||||||
new "start restconf daemon -b"
|
new "start restconf daemon -b"
|
||||||
start_restconf -f $cfg -b
|
start_restconf -f $cfg -b
|
||||||
else
|
else
|
||||||
|
|
@ -88,21 +137,21 @@ testrun()
|
||||||
wait_restconf
|
wait_restconf
|
||||||
|
|
||||||
new "restconf root discovery. RFC 8040 3.1 (xml+xrd)"
|
new "restconf root discovery. RFC 8040 3.1 (xml+xrd)"
|
||||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>"
|
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>"
|
||||||
|
|
||||||
new "restconf get restconf resource. RFC 8040 3.3 (json)"
|
new "restconf get restconf resource. RFC 8040 3.3 (json)"
|
||||||
expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf)" 0 'HTTP/1.1 200 OK' '{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2019-01-04"}}'
|
expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $proto://$addr/restconf)" 0 'HTTP/1.1 200 OK' '{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2019-01-04"}}'
|
||||||
|
|
||||||
new "restconf get restconf resource. RFC 8040 3.3 (xml)"
|
new "restconf get restconf resource. RFC 8040 3.3 (xml)"
|
||||||
# Get XML instead of JSON?
|
# Get XML instead of JSON?
|
||||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf)" 0 'HTTP/1.1 200 OK' '<restconf xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><data/><operations/><yang-library-version>2019-01-04</yang-library-version></restconf>'
|
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $proto://$addr/restconf)" 0 'HTTP/1.1 200 OK' '<restconf xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><data/><operations/><yang-library-version>2019-01-04</yang-library-version></restconf>'
|
||||||
|
|
||||||
# Should be alphabetically ordered
|
# Should be alphabetically ordered
|
||||||
new "restconf get restconf/operations. RFC8040 3.3.2 (json)"
|
new "restconf get restconf/operations. RFC8040 3.3.2 (json)"
|
||||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/operations)" 0 'HTTP/1.1 200 OK' '{"operations":{"clixon-example:client-rpc":\[null\],"clixon-example:empty":\[null\],"clixon-example:optional":\[null\],"clixon-example:example":\[null\],"clixon-lib:debug":\[null\],"clixon-lib:ping":\[null\],"clixon-lib:stats":\[null\],"clixon-lib:restart-plugin":\[null\],"ietf-netconf:get-config":\[null\],"ietf-netconf:edit-config":\[null\],"ietf-netconf:copy-config":\[null\],"ietf-netconf:delete-config":\[null\],"ietf-netconf:lock":\[null\],"ietf-netconf:unlock":\[null\],"ietf-netconf:get":\[null\],"ietf-netconf:close-session":\[null\],"ietf-netconf:kill-session":\[null\],"ietf-netconf:commit":\[null\],"ietf-netconf:discard-changes":\[null\],"ietf-netconf:validate":\[null\]'
|
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/operations)" 0 'HTTP/1.1 200 OK' '{"operations":{"clixon-example:client-rpc":\[null\],"clixon-example:empty":\[null\],"clixon-example:optional":\[null\],"clixon-example:example":\[null\],"clixon-lib:debug":\[null\],"clixon-lib:ping":\[null\],"clixon-lib:stats":\[null\],"clixon-lib:restart-plugin":\[null\],"ietf-netconf:get-config":\[null\],"ietf-netconf:edit-config":\[null\],"ietf-netconf:copy-config":\[null\],"ietf-netconf:delete-config":\[null\],"ietf-netconf:lock":\[null\],"ietf-netconf:unlock":\[null\],"ietf-netconf:get":\[null\],"ietf-netconf:close-session":\[null\],"ietf-netconf:kill-session":\[null\],"ietf-netconf:commit":\[null\],"ietf-netconf:discard-changes":\[null\],"ietf-netconf:validate":\[null\]'
|
||||||
|
|
||||||
new "restconf get restconf/operations. RFC8040 3.3.2 (xml)"
|
new "restconf get restconf/operations. RFC8040 3.3.2 (xml)"
|
||||||
ret=$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/operations)
|
ret=$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $proto://$addr/restconf/operations)
|
||||||
expect='<operations><client-rpc xmlns="urn:example:clixon"/><empty xmlns="urn:example:clixon"/><optional xmlns="urn:example:clixon"/><example xmlns="urn:example:clixon"/><debug xmlns="http://clicon.org/lib"/><ping xmlns="http://clicon.org/lib"/><stats xmlns="http://clicon.org/lib"/><restart-plugin xmlns="http://clicon.org/lib"/><get-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><copy-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><delete-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><lock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><unlock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><close-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><kill-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><commit xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><discard-changes xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><validate xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>'
|
expect='<operations><client-rpc xmlns="urn:example:clixon"/><empty xmlns="urn:example:clixon"/><optional xmlns="urn:example:clixon"/><example xmlns="urn:example:clixon"/><debug xmlns="http://clicon.org/lib"/><ping xmlns="http://clicon.org/lib"/><stats xmlns="http://clicon.org/lib"/><restart-plugin xmlns="http://clicon.org/lib"/><get-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><copy-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><delete-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><lock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><unlock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><close-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><kill-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><commit xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><discard-changes xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><validate xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>'
|
||||||
match=`echo $ret | grep --null -Eo "$expect"`
|
match=`echo $ret | grep --null -Eo "$expect"`
|
||||||
if [ -z "$match" ]; then
|
if [ -z "$match" ]; then
|
||||||
|
|
@ -110,10 +159,10 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPR
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "restconf get restconf/yang-library-version. RFC8040 3.3.3"
|
new "restconf get restconf/yang-library-version. RFC8040 3.3.3"
|
||||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/yang-library-version)" 0 'HTTP/1.1 200 OK' '{"yang-library-version":"2019-01-04"}'
|
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/yang-library-version)" 0 'HTTP/1.1 200 OK' '{"yang-library-version":"2019-01-04"}'
|
||||||
|
|
||||||
new "restconf get restconf/yang-library-version. RFC8040 3.3.3 (xml)"
|
new "restconf get restconf/yang-library-version. RFC8040 3.3.3 (xml)"
|
||||||
ret=$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/yang-library-version)
|
ret=$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $proto://$addr/restconf/yang-library-version)
|
||||||
expect="<yang-library-version>2019-01-04</yang-library-version>"
|
expect="<yang-library-version>2019-01-04</yang-library-version>"
|
||||||
match=`echo $ret | grep --null -Eo "$expect"`
|
match=`echo $ret | grep --null -Eo "$expect"`
|
||||||
if [ -z "$match" ]; then
|
if [ -z "$match" ]; then
|
||||||
|
|
@ -121,48 +170,48 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPR
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895 (explicit resource)"
|
new "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895 (explicit resource)"
|
||||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-yang-library:modules-state/module=ietf-interfaces,2018-02-20)" 0 'HTTP/1.1 200 OK' '{"ietf-yang-library:module":\[{"name":"ietf-interfaces","revision":"2018-02-20","namespace":"urn:ietf:params:xml:ns:yang:ietf-interfaces","conformance-type":"implement"}\]}'
|
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-yang-library:modules-state/module=ietf-interfaces,2018-02-20)" 0 'HTTP/1.1 200 OK' '{"ietf-yang-library:module":\[{"name":"ietf-interfaces","revision":"2018-02-20","namespace":"urn:ietf:params:xml:ns:yang:ietf-interfaces","conformance-type":"implement"}\]}'
|
||||||
|
|
||||||
new "restconf schema resource, mod-state top-level"
|
new "restconf schema resource, mod-state top-level"
|
||||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-yang-library:modules-state)" 0 'HTTP/1.1 200 OK' '{"ietf-yang-library:modules-state":{"module-set-id":"0","module":\[{"name":"clixon-example","revision":"2020-03-11","namespace":"urn:example:clixon","conformance-type":"implement"},{"name":"clixon-lib","revision":"2020-04-23","'
|
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-yang-library:modules-state)" 0 'HTTP/1.1 200 OK' '{"ietf-yang-library:modules-state":{"module-set-id":"0","module":\[{"name":"clixon-example","revision":"2020-03-11","namespace":"urn:example:clixon","conformance-type":"implement"},{"name":"clixon-lib","revision":"2020-04-23","'
|
||||||
|
|
||||||
new "restconf options. RFC 8040 4.1"
|
new "restconf options. RFC 8040 4.1"
|
||||||
expectpart "$(curl $CURLOPTS -X OPTIONS $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE"
|
expectpart "$(curl $CURLOPTS -X OPTIONS $proto://$addr/restconf/data)" 0 "HTTP/1.1 200 OK" "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE"
|
||||||
|
|
||||||
# -I means HEAD
|
# -I means HEAD
|
||||||
new "restconf HEAD. RFC 8040 4.2"
|
new "restconf HEAD. RFC 8040 4.2"
|
||||||
expectpart "$(curl $CURLOPTS -I -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+json"
|
expectpart "$(curl $CURLOPTS -I -H "Accept: application/yang-data+json" $proto://$addr/restconf/data)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+json"
|
||||||
|
|
||||||
new "restconf empty rpc JSON"
|
new "restconf empty rpc JSON"
|
||||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content"
|
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":null} $proto://$addr/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content"
|
||||||
|
|
||||||
new "restconf empty rpc XML"
|
new "restconf empty rpc XML"
|
||||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" -d '<input xmlns="urn:example:clixon"></input>' $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content"
|
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" -d '<input xmlns="urn:example:clixon"></input>' $proto://$addr/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content"
|
||||||
|
|
||||||
new "restconf empty rpc, default media type should fail"
|
new "restconf empty rpc, default media type should fail"
|
||||||
expectpart "$(curl $CURLOPTS -X POST -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type'
|
expectpart "$(curl $CURLOPTS -X POST -d {\"clixon-example:input\":null} $proto://$addr/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type'
|
||||||
|
|
||||||
new "restconf empty rpc, default media type should fail (JSON)"
|
new "restconf empty rpc, default media type should fail (JSON)"
|
||||||
expectpart "$(curl $CURLOPTS -X POST -H "Accept: application/yang-data+json" -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type'
|
expectpart "$(curl $CURLOPTS -X POST -H "Accept: application/yang-data+json" -d {\"clixon-example:input\":null} $proto://$addr/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type'
|
||||||
|
|
||||||
new "restconf empty rpc with extra args (should fail)"
|
new "restconf empty rpc with extra args (should fail)"
|
||||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":{\"extra\":null}} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"Unrecognized parameter: extra in rpc: empty"}}}'
|
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":{\"extra\":null}} $proto://$addr/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"Unrecognized parameter: extra in rpc: empty"}}}'
|
||||||
|
|
||||||
# Irritiating to get debugs on the terminal
|
# Irritiating to get debugs on the terminal
|
||||||
#new "restconf debug rpc"
|
#new "restconf debug rpc"
|
||||||
#expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-lib:input\":{\"level\":0}} $RCPROTO://localhost/restconf/operations/clixon-lib:debug)" 0 "HTTP/1.1 204 No Content"
|
#expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-lib:input\":{\"level\":0}} $proto://$addr/restconf/operations/clixon-lib:debug)" 0 "HTTP/1.1 204 No Content"
|
||||||
|
|
||||||
new "restconf get empty config + state json"
|
new "restconf get empty config + state json"
|
||||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" '{"clixon-example:state":{"op":\["41","42","43"\]}}'
|
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" '{"clixon-example:state":{"op":\["41","42","43"\]}}'
|
||||||
|
|
||||||
new "restconf get empty config + state json with wrong module name"
|
new "restconf get empty config + state json with wrong module name"
|
||||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/badmodule:state)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"badmodule"},"error-severity":"error","error-message":"No such yang module prefix"}}}'
|
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/badmodule:state)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"badmodule"},"error-severity":"error","error-message":"No such yang module prefix"}}}'
|
||||||
|
|
||||||
#'HTTP/1.1 404 Not Found'
|
#'HTTP/1.1 404 Not Found'
|
||||||
#'{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"No such yang module: badmodule"}}}'
|
#'{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"No such yang module: badmodule"}}}'
|
||||||
|
|
||||||
new "restconf get empty config + state xml"
|
new "restconf get empty config + state xml"
|
||||||
ret=$(curl $CURLOPTS -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)
|
ret=$(curl $CURLOPTS -H "Accept: application/yang-data+xml" -X GET $proto://$addr/restconf/data/clixon-example:state)
|
||||||
expect='<state xmlns="urn:example:clixon"><op>41</op><op>42</op><op>43</op></state>'
|
expect='<state xmlns="urn:example:clixon"><op>41</op><op>42</op><op>43</op></state>'
|
||||||
match=`echo $ret | grep --null -Eo "$expect"`
|
match=`echo $ret | grep --null -Eo "$expect"`
|
||||||
if [ -z "$match" ]; then
|
if [ -z "$match" ]; then
|
||||||
|
|
@ -170,11 +219,11 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPR
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "restconf get data type json"
|
new "restconf get data type json"
|
||||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"}'
|
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"}'
|
||||||
|
|
||||||
new "restconf get state operation"
|
new "restconf get state operation"
|
||||||
# Cant get shell macros to work, inline matching from lib.sh
|
# Cant get shell macros to work, inline matching from lib.sh
|
||||||
ret=$(curl $CURLOPTS -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)
|
ret=$(curl $CURLOPTS -H "Accept: application/yang-data+xml" -X GET $proto://$addr/restconf/data/clixon-example:state/op=42)
|
||||||
expect='<op xmlns="urn:example:clixon">42</op>'
|
expect='<op xmlns="urn:example:clixon">42</op>'
|
||||||
match=`echo $ret | grep --null -Eo "$expect"`
|
match=`echo $ret | grep --null -Eo "$expect"`
|
||||||
if [ -z "$match" ]; then
|
if [ -z "$match" ]; then
|
||||||
|
|
@ -182,11 +231,11 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPR
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "restconf get state operation type json"
|
new "restconf get state operation type json"
|
||||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"}'
|
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"}'
|
||||||
|
|
||||||
new "restconf get state operation type xml"
|
new "restconf get state operation type xml"
|
||||||
# Cant get shell macros to work, inline matching from lib.sh
|
# Cant get shell macros to work, inline matching from lib.sh
|
||||||
ret=$(curl $CURLOPTS -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)
|
ret=$(curl $CURLOPTS -H "Accept: application/yang-data+xml" -X GET $proto://$addr/restconf/data/clixon-example:state/op=42)
|
||||||
expect='<op xmlns="urn:example:clixon">42</op>'
|
expect='<op xmlns="urn:example:clixon">42</op>'
|
||||||
match=`echo $ret | grep --null -Eo "$expect"`
|
match=`echo $ret | grep --null -Eo "$expect"`
|
||||||
if [ -z "$match" ]; then
|
if [ -z "$match" ]; then
|
||||||
|
|
@ -194,88 +243,88 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPR
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "restconf GET datastore"
|
new "restconf GET datastore"
|
||||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" '{"clixon-example:state":{"op":\["41","42","43"\]}}'
|
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" '{"clixon-example:state":{"op":\["41","42","43"\]}}'
|
||||||
|
|
||||||
# Exact match
|
# Exact match
|
||||||
new "restconf Add subtree eth/0/0 to datastore using POST"
|
new "restconf Add subtree eth/0/0 to datastore using POST"
|
||||||
expectpart "$(curl $CURLOPTS -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $RCPROTO://localhost/restconf/data)" 0 'HTTP/1.1 201 Created' "Location: $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces"
|
expectpart "$(curl $CURLOPTS -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $proto://$addr/restconf/data)" 0 'HTTP/1.1 201 Created' "Location: $proto://$addr/restconf/data/ietf-interfaces:interfaces"
|
||||||
|
|
||||||
new "restconf Re-add subtree eth/0/0 which should give error"
|
new "restconf Re-add subtree eth/0/0 which should give error"
|
||||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $RCPROTO://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}'
|
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $proto://$addr/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}'
|
||||||
|
|
||||||
new "restconf Check interfaces eth/0/0 added"
|
new "restconf Check interfaces eth/0/0 added"
|
||||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
|
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
|
||||||
|
|
||||||
new "restconf delete interfaces"
|
new "restconf delete interfaces"
|
||||||
expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 204 No Content"
|
expectpart "$(curl $CURLOPTS -X DELETE $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 204 No Content"
|
||||||
|
|
||||||
new "restconf Check empty config"
|
new "restconf Check empty config"
|
||||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" "$state"
|
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" "$state"
|
||||||
|
|
||||||
new "restconf Add interfaces subtree eth/0/0 using POST"
|
new "restconf Add interfaces subtree eth/0/0 using POST"
|
||||||
expectpart "$(curl $CURLOPTS -X POST $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}')" 0 "HTTP/1.1 201 Created"
|
expectpart "$(curl $CURLOPTS -X POST $proto://$addr/restconf/data/ietf-interfaces:interfaces -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}')" 0 "HTTP/1.1 201 Created"
|
||||||
|
|
||||||
new "restconf Check eth/0/0 added config"
|
new "restconf Check eth/0/0 added config"
|
||||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
|
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
|
||||||
|
|
||||||
new "restconf Check eth/0/0 GET augmented state level 1"
|
new "restconf Check eth/0/0 GET augmented state level 1"
|
||||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}'
|
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}'
|
||||||
|
|
||||||
new "restconf Check eth/0/0 GET augmented state level 2"
|
new "restconf Check eth/0/0 GET augmented state level 2"
|
||||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0/clixon-example:my-status)" 0 'HTTP/1.1 200 OK' '{"clixon-example:my-status":{"int":42,"str":"foo"}}'
|
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0/clixon-example:my-status)" 0 'HTTP/1.1 200 OK' '{"clixon-example:my-status":{"int":42,"str":"foo"}}'
|
||||||
|
|
||||||
new "restconf Check eth/0/0 added state XXXXXXX"
|
new "restconf Check eth/0/0 added state XXXXXXX"
|
||||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 'HTTP/1.1 200 OK' '{"clixon-example:state":{"op":\["41","42","43"\]}}'
|
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/clixon-example:state)" 0 'HTTP/1.1 200 OK' '{"clixon-example:state":{"op":\["41","42","43"\]}}'
|
||||||
|
|
||||||
new "restconf Re-post eth/0/0 which should generate error"
|
new "restconf Re-post eth/0/0 which should generate error"
|
||||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}
'
|
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}
'
|
||||||
|
|
||||||
new "Add leaf description using POST"
|
new "Add leaf description using POST"
|
||||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:description":"The-first-interface"}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created"
|
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:description":"The-first-interface"}' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created"
|
||||||
|
|
||||||
new "Add nothing using POST (expect fail)"
|
new "Add nothing using POST (expect fail)"
|
||||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"The message-body MUST contain exactly one instance of the expected data resource"}}}'
|
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"The message-body MUST contain exactly one instance of the expected data resource"}}}'
|
||||||
|
|
||||||
new "restconf Check description added"
|
new "restconf Check description added"
|
||||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","description":"The-first-interface","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
|
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","description":"The-first-interface","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
|
||||||
|
|
||||||
new "restconf delete eth/0/0"
|
new "restconf delete eth/0/0"
|
||||||
expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 204 No Content"
|
expectpart "$(curl $CURLOPTS -X DELETE $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 204 No Content"
|
||||||
|
|
||||||
new "Check deleted eth/0/0"
|
new "Check deleted eth/0/0"
|
||||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "$state"
|
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data)" 0 "HTTP/1.1 200 OK" "$state"
|
||||||
|
|
||||||
new "restconf Re-Delete eth/0/0 using none should generate error"
|
new "restconf Re-Delete eth/0/0 using none should generate error"
|
||||||
expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 409 Conflict" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-missing","error-severity":"error","error-message":"Data does not exist; cannot delete resource"}}}'
|
expectpart "$(curl $CURLOPTS -X DELETE $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 409 Conflict" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-missing","error-severity":"error","error-message":"Data does not exist; cannot delete resource"}}}'
|
||||||
|
|
||||||
new "restconf Add subtree eth/0/0 using PUT"
|
new "restconf Add subtree eth/0/0 using PUT"
|
||||||
expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created"
|
expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created"
|
||||||
|
|
||||||
new "restconf get subtree"
|
new "restconf get subtree"
|
||||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
|
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
|
||||||
|
|
||||||
new "restconf rpc using POST json"
|
new "restconf rpc using POST json"
|
||||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 "HTTP/1.1 200 OK" '{"clixon-example:output":{"x":"42","y":"42"}}'
|
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' $proto://$addr/restconf/operations/clixon-example:example)" 0 "HTTP/1.1 200 OK" '{"clixon-example:output":{"x":"42","y":"42"}}'
|
||||||
|
|
||||||
if ! $YANG_UNKNOWN_ANYDATA ; then
|
if ! $YANG_UNKNOWN_ANYDATA ; then
|
||||||
new "restconf rpc using POST json wrong"
|
new "restconf rpc using POST json wrong"
|
||||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: wrongelement with parent: example in namespace: urn:example:clixon"}}}'
|
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' $proto://$addr/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: wrongelement with parent: example in namespace: urn:example:clixon"}}}'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "restconf rpc non-existing rpc without namespace"
|
new "restconf rpc non-existing rpc without namespace"
|
||||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $RCPROTO://localhost/restconf/operations/kalle)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}'
|
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $proto://$addr/restconf/operations/kalle)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}'
|
||||||
|
|
||||||
new "restconf rpc non-existing rpc"
|
new "restconf rpc non-existing rpc"
|
||||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $RCPROTO://localhost/restconf/operations/clixon-example:kalle)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}'
|
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $proto://$addr/restconf/operations/clixon-example:kalle)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}'
|
||||||
|
|
||||||
new "restconf rpc missing name"
|
new "restconf rpc missing name"
|
||||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $RCPROTO://localhost/restconf/operations)" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"Operation name expected"}}}'
|
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $proto://$addr/restconf/operations)" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"Operation name expected"}}}'
|
||||||
|
|
||||||
new "restconf rpc missing input"
|
new "restconf rpc missing input"
|
||||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"restconf RPC does not have input statement"}}}'
|
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $proto://$addr/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"restconf RPC does not have input statement"}}}'
|
||||||
|
|
||||||
new "restconf rpc using POST xml"
|
new "restconf rpc using POST xml"
|
||||||
ret=$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":42}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)
|
ret=$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":42}}' $proto://$addr/restconf/operations/clixon-example:example)
|
||||||
expect='<output xmlns="urn:example:clixon"><x>42</x><y>42</y></output>'
|
expect='<output xmlns="urn:example:clixon"><x>42</x><y>42</y></output>'
|
||||||
match=`echo $ret | grep --null -Eo "$expect"`
|
match=`echo $ret | grep --null -Eo "$expect"`
|
||||||
if [ -z "$match" ]; then
|
if [ -z "$match" ]; then
|
||||||
|
|
@ -283,10 +332,10 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPR
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "restconf rpc using wrong prefix"
|
new "restconf rpc using wrong prefix"
|
||||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' $RCPROTO://localhost/restconf/operations/wrong:example)" 0 "HTTP/1.1 412 Precondition Failed" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"yang module not found"}}}'
|
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' $proto://$addr/restconf/operations/wrong:example)" 0 "HTTP/1.1 412 Precondition Failed" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"yang module not found"}}}'
|
||||||
|
|
||||||
new "restconf local client rpc using POST xml"
|
new "restconf local client rpc using POST xml"
|
||||||
ret=$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":"example"}}' $RCPROTO://localhost/restconf/operations/clixon-example:client-rpc)
|
ret=$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":"example"}}' $proto://$addr/restconf/operations/clixon-example:client-rpc)
|
||||||
expect='<output xmlns="urn:example:clixon"><x>example</x></output>'
|
expect='<output xmlns="urn:example:clixon"><x>example</x></output>'
|
||||||
match=`echo $ret | grep --null -Eo "$expect"`
|
match=`echo $ret | grep --null -Eo "$expect"`
|
||||||
if [ -z "$match" ]; then
|
if [ -z "$match" ]; then
|
||||||
|
|
@ -294,10 +343,10 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPR
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "restconf Add subtree without key (expected error)"
|
new "restconf Add subtree without key (expected error)"
|
||||||
expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =interface, expected'
|
expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =interface, expected'
|
||||||
|
|
||||||
new "restconf Add subtree with too many keys (expected error)"
|
new "restconf Add subtree with too many keys (expected error)"
|
||||||
expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 "HTTP/1.1 400 Bad Request" '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}}'
|
expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 "HTTP/1.1 400 Bad Request" '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}}'
|
||||||
|
|
||||||
if [ $RC -ne 0 ]; then
|
if [ $RC -ne 0 ]; then
|
||||||
new "Kill restconf daemon"
|
new "Kill restconf daemon"
|
||||||
|
|
@ -316,10 +365,25 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPR
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
new "Use local restconf config"
|
# Go thru all combinations of IPv4/IPv6, http/https, local/backend config
|
||||||
testrun false
|
protos="http"
|
||||||
|
if [ "${WITH_RESTCONF}" = "evhtp" ]; then
|
||||||
new "Get restconf config from backend"
|
# http only relevant for evhtp (for fcgi: need nginx config)
|
||||||
testrun true
|
protos="$protos https"
|
||||||
|
fi
|
||||||
|
for proto in $protos; do
|
||||||
|
for addr in 127.0.0.1 "\[::1\]"; do
|
||||||
|
configs="local"
|
||||||
|
if [ "${WITH_RESTCONF}" = "evhtp" ]; then
|
||||||
|
# backend config retrieval only implemented for evhtp
|
||||||
|
configs="$configs backend"
|
||||||
|
fi
|
||||||
|
echo "configs:$configs"
|
||||||
|
for config in $configs; do
|
||||||
|
new "restconf test: proto:$proto addr:$addr config:$config"
|
||||||
|
testrun $proto $addr $config
|
||||||
|
done
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
rm -rf $dir
|
rm -rf $dir
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Restconf basic functionality
|
# Restconf basic functionality
|
||||||
# Assume http server setup, such as nginx described in apps/restconf/README.md
|
# Assume http server setup, such as nginx described in apps/restconf/README.md
|
||||||
|
# Systematic tests of restconf operations GET/POST/PUT/DELETE
|
||||||
|
# See also test_restconf.sh
|
||||||
|
|
||||||
# Magic line must be first in script (see README.md)
|
# Magic line must be first in script (see README.md)
|
||||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,12 @@ fyang=$dir/example.yang
|
||||||
|
|
||||||
cfg=$dir/conf.xml
|
cfg=$dir/conf.xml
|
||||||
|
|
||||||
|
# Local for test here
|
||||||
certdir=$dir/certs
|
certdir=$dir/certs
|
||||||
srvkey=$certdir/srv_key.pem
|
srvkey=$certdir/srv_key.pem
|
||||||
srvcert=$certdir/srv_cert.pem
|
srvcert=$certdir/srv_cert.pem
|
||||||
cakey=$certdir/ca_key.pem # needed?
|
cakey=$certdir/ca_key.pem # needed?
|
||||||
|
|
||||||
cacert=$certdir/ca_cert.pem
|
cacert=$certdir/ca_cert.pem
|
||||||
|
|
||||||
users="andy guest" # generate certs for some users in nacm.sh
|
users="andy guest" # generate certs for some users in nacm.sh
|
||||||
|
|
@ -117,68 +119,9 @@ EOF
|
||||||
)
|
)
|
||||||
|
|
||||||
if $genkeys; then
|
if $genkeys; then
|
||||||
# Create certs
|
|
||||||
# 1. CA
|
|
||||||
cat<<EOF > $dir/ca.cnf
|
|
||||||
[ ca ]
|
|
||||||
default_ca = CA_default
|
|
||||||
|
|
||||||
[ CA_default ]
|
# Server certs
|
||||||
serial = ca-serial
|
. ./certs.sh
|
||||||
crl = ca-crl.pem
|
|
||||||
database = ca-database.txt
|
|
||||||
name_opt = CA_default
|
|
||||||
cert_opt = CA_default
|
|
||||||
default_crl_days = 9999
|
|
||||||
default_md = md5
|
|
||||||
|
|
||||||
[ req ]
|
|
||||||
default_bits = 2048
|
|
||||||
days = 1
|
|
||||||
distinguished_name = req_distinguished_name
|
|
||||||
attributes = req_attributes
|
|
||||||
prompt = no
|
|
||||||
output_password = password
|
|
||||||
|
|
||||||
[ req_distinguished_name ]
|
|
||||||
C = SE
|
|
||||||
L = Stockholm
|
|
||||||
O = Clixon
|
|
||||||
OU = clixon
|
|
||||||
CN = ca
|
|
||||||
emailAddress = olof@hagsand.se
|
|
||||||
|
|
||||||
[ req_attributes ]
|
|
||||||
challengePassword = test
|
|
||||||
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Generate CA cert
|
|
||||||
openssl req -x509 -days 1 -config $dir/ca.cnf -keyout $cakey -out $cacert
|
|
||||||
|
|
||||||
cat<<EOF > $dir/srv.cnf
|
|
||||||
[req]
|
|
||||||
prompt = no
|
|
||||||
distinguished_name = dn
|
|
||||||
req_extensions = ext
|
|
||||||
[dn]
|
|
||||||
CN = www.clicon.org # localhost
|
|
||||||
emailAddress = olof@hagsand.se
|
|
||||||
O = Clixon
|
|
||||||
L = Stockholm
|
|
||||||
C = SE
|
|
||||||
[ext]
|
|
||||||
subjectAltName = DNS:clicon.org
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Generate server key
|
|
||||||
openssl genrsa -out $srvkey 2048
|
|
||||||
|
|
||||||
# Generate CSR (signing request)
|
|
||||||
openssl req -new -config $dir/srv.cnf -key $srvkey -out $certdir/srv_csr.pem
|
|
||||||
|
|
||||||
# Sign server cert by CA
|
|
||||||
openssl x509 -req -extfile $dir/srv.cnf -days 1 -passin "pass:password" -in $certdir/srv_csr.pem -CA $cacert -CAkey $cakey -CAcreateserial -out $srvcert
|
|
||||||
|
|
||||||
# create client certs
|
# create client certs
|
||||||
for name in $users; do
|
for name in $users; do
|
||||||
|
|
@ -201,7 +144,7 @@ EOF
|
||||||
|
|
||||||
# Sign by CA
|
# Sign by CA
|
||||||
openssl x509 -req -extfile $dir/$name.cnf -days 1 -passin "pass:password" -in $certdir/$name.csr -CA $cacert -CAkey $cakey -CAcreateserial -out $certdir/$name.crt
|
openssl x509 -req -extfile $dir/$name.cnf -days 1 -passin "pass:password" -in $certdir/$name.csr -CA $cacert -CAkey $cakey -CAcreateserial -out $certdir/$name.crt
|
||||||
done
|
done # client key
|
||||||
|
|
||||||
fi # genkeys
|
fi # genkeys
|
||||||
|
|
||||||
|
|
@ -226,16 +169,18 @@ testrun()
|
||||||
cat <<EOF > $dir/startup_db
|
cat <<EOF > $dir/startup_db
|
||||||
<config>
|
<config>
|
||||||
<restconf xmlns="https://clicon.org/restconf">
|
<restconf xmlns="https://clicon.org/restconf">
|
||||||
|
<auth-type>$authtype</auth-type>
|
||||||
|
<ssl-enable>true</ssl-enable>
|
||||||
|
<server-cert-path>$srvcert</server-cert-path>
|
||||||
|
<server-key-path>$srvkey</server-key-path>
|
||||||
|
<server-ca-cert-path>$cacert</server-ca-cert-path>
|
||||||
|
|
||||||
<socket>
|
<socket>
|
||||||
<namespace>default</namespace>
|
<namespace>default</namespace>
|
||||||
<address>0.0.0.0</address>
|
<address>0.0.0.0</address>
|
||||||
<port>$port</port>
|
<port>$port</port>
|
||||||
<ssl>$ssl</ssl>
|
<ssl>$ssl</ssl>
|
||||||
</socket>
|
</socket>
|
||||||
<auth-type>$authtype</auth-type>
|
|
||||||
<server-cert-path>$srvcert</server-cert-path>
|
|
||||||
<server-key-path>$srvkey</server-key-path>
|
|
||||||
<server-ca-cert-path>$cacert</server-ca-cert-path>
|
|
||||||
</restconf>
|
</restconf>
|
||||||
$RULES
|
$RULES
|
||||||
</config>
|
</config>
|
||||||
|
|
@ -259,6 +204,9 @@ EOF
|
||||||
start_backend -s startup -f $cfg
|
start_backend -s startup -f $cfg
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
new "wait for backend"
|
||||||
|
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
|
||||||
|
|
@ -271,9 +219,6 @@ EOF
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "wait for backend"
|
|
||||||
wait_backend
|
|
||||||
|
|
||||||
new "wait for restconf"
|
new "wait for restconf"
|
||||||
wait_restconf --key $certdir/andy.key --cert $certdir/andy.crt
|
wait_restconf --key $certdir/andy.key --cert $certdir/andy.crt
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,10 +57,6 @@ module clixon-restconf {
|
||||||
description
|
description
|
||||||
"PAM password auth";
|
"PAM password auth";
|
||||||
}
|
}
|
||||||
enum none {
|
|
||||||
description
|
|
||||||
"No authentication, no security.";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
description
|
description
|
||||||
"Enumeration of HTTP authorization types.";
|
"Enumeration of HTTP authorization types.";
|
||||||
|
|
@ -91,31 +87,22 @@ module clixon-restconf {
|
||||||
presence "Enables RESTCONF";
|
presence "Enables RESTCONF";
|
||||||
description
|
description
|
||||||
"HTTP daemon configuration.";
|
"HTTP daemon configuration.";
|
||||||
list socket {
|
leaf ssl-enable {
|
||||||
key "namespace address port";
|
description
|
||||||
leaf namespace {
|
"Enable ssl server functionality.
|
||||||
type string;
|
Setting to false means the following are invalid:
|
||||||
description "indicates a namespace for instance. On platforms where namespaces are not suppported, always 'default'";
|
- auth-type=client-certificate
|
||||||
}
|
- socket entries with ssl=true
|
||||||
leaf address {
|
Also, the following are not releveant: server-cert-path, server-key-path,
|
||||||
type inet:ip-address;
|
server-ca-cert-path";
|
||||||
description "IP address to bind to";
|
|
||||||
}
|
|
||||||
leaf port {
|
|
||||||
type inet:port-number;
|
|
||||||
description "IP port to bind to";
|
|
||||||
}
|
|
||||||
leaf ssl {
|
|
||||||
type boolean;
|
type boolean;
|
||||||
default true;
|
default false;
|
||||||
description "Enable for HTTPS otherwise HTTP protocol";
|
|
||||||
}
|
}
|
||||||
}
|
leaf-list auth-type {
|
||||||
leaf auth-type {
|
|
||||||
type http-auth-type;
|
type http-auth-type;
|
||||||
description
|
description
|
||||||
"The authentication type.
|
"The authentication type.
|
||||||
Note client-certificate applies only if socket has ssl enabled";
|
Note client-certificate applies only if ssl-enable is true and socket has ssl";
|
||||||
}
|
}
|
||||||
leaf server-cert-path {
|
leaf server-cert-path {
|
||||||
type string;
|
type string;
|
||||||
|
|
@ -141,6 +128,26 @@ module clixon-restconf {
|
||||||
default "/etc/ssl/certs/clixon-ca_crt.pem";
|
default "/etc/ssl/certs/clixon-ca_crt.pem";
|
||||||
/* CLICON_SSL_CA_CERT */
|
/* CLICON_SSL_CA_CERT */
|
||||||
}
|
}
|
||||||
|
list socket {
|
||||||
|
key "namespace address port";
|
||||||
|
leaf namespace {
|
||||||
|
type string;
|
||||||
|
description "indicates a namespace for instance. On platforms where namespaces are not suppported, always 'default'";
|
||||||
|
}
|
||||||
|
leaf address {
|
||||||
|
type inet:ip-address;
|
||||||
|
description "IP address to bind to";
|
||||||
|
}
|
||||||
|
leaf port {
|
||||||
|
type inet:port-number;
|
||||||
|
description "IP port to bind to";
|
||||||
|
}
|
||||||
|
leaf ssl {
|
||||||
|
type boolean;
|
||||||
|
default true;
|
||||||
|
description "Enable for HTTPS otherwise HTTP protocol";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
rpc restconf-control {
|
rpc restconf-control {
|
||||||
input {
|
input {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue