diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 7afe54af..7afa8091 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -297,7 +297,7 @@ check_drop_priv(clicon_handle h, clicon_err(OE_DAEMON, EPERM, "Privileges can only be dropped from root user (uid is %u)\n", uid); goto done; } - /* When dropping priveleges, datastores are created if they do not exist. + /* When dropping privileges, datastores are created if they do not exist. * But when drops are not made, datastores are created on demand. * XXX: move the creation to top-level so they are always created at init? */ diff --git a/apps/restconf/Makefile.in b/apps/restconf/Makefile.in index 92a9124b..2804f70d 100644 --- a/apps/restconf/Makefile.in +++ b/apps/restconf/Makefile.in @@ -78,35 +78,30 @@ CPPFLAGS = @CPPFLAGS@ -fPIC INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@ -# Applications -ifeq ($(with_restconf),fcgi) -APPL = clixon_restconf # fcgi / nginx -else -APPL = clixon_restconf_$(with_restconf) -endif +# Application +APPL = clixon_restconf # Common source - not accessible from plugin - independent of restconf package (fcgi|evhtp) -#APPSRC = restconf_lib.c +APPSRC = +APPSRC += restconf_api.c # maybe empty +APPSRC += restconf_api_$(with_restconf).c # cant be .so since libevhtp is a. +APPSRC += restconf_root.c +APPSRC += restconf_$(with_restconf)_main.c # Fcgi-specific source including main ifeq ($(with_restconf),fcgi) -APPSRC = restconf_fcgi_lib.c -APPSRC += restconf_fcgi_main.c -APPSRC += restconf_methods.c # These should be moved ^ +APPSRC += restconf_fcgi_lib.c # Most of these should be made generic +APPSRC += restconf_methods.c APPSRC += restconf_methods_post.c APPSRC += restconf_methods_get.c APPSRC += restconf_stream.c endif -# Evhtp-specific source including main -ifeq ($(with_restconf),evhtp) -APPSRC = restconf_evhtp_main.c -endif - APPOBJ = $(APPSRC:.c=.o) # Accessible from plugin LIBSRC = restconf_lib.c + LIBOBJ = $(LIBSRC:.c=.o) # This lib is very small but used for clixon restconf applications to access clixon restconf lib diff --git a/apps/restconf/restconf_api.c b/apps/restconf/restconf_api.c new file mode 100644 index 00000000..26638858 --- /dev/null +++ b/apps/restconf/restconf_api.c @@ -0,0 +1,66 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + * Generic restconf API functions + */ + + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* chmod */ + +/* cligen */ +#include + +/* clicon */ +#include + +/* restconf */ +#include "restconf_lib.h" +#include "restconf_api.h" + diff --git a/apps/restconf/restconf_api.h b/apps/restconf/restconf_api.h new file mode 100644 index 00000000..50084654 --- /dev/null +++ b/apps/restconf/restconf_api.h @@ -0,0 +1,54 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + * + * Virtual clixon restconf API functions. + */ + +#ifndef _RESTCONF_API_H_ +#define _RESTCONF_API_H_ + +/* + * Prototypes + */ +int restconf_reply_status_code(void *req, int code); + +#if defined(__GNUC__) && __GNUC__ >= 3 +int restconf_reply_header_add(void *req, char *name, char *vfmt, ...) __attribute__ ((format (printf, 3, 4))); +#else +int restconf_reply_header_add(FCGX_Request *req, char *name, char *vfmt, ...); +#endif + +int restconf_reply_send(void *req, cbuf *cb); + +#endif /* _RESTCONF_API_H_ */ diff --git a/apps/restconf/restconf_api_evhtp.c b/apps/restconf/restconf_api_evhtp.c new file mode 100644 index 00000000..be3d4aa6 --- /dev/null +++ b/apps/restconf/restconf_api_evhtp.c @@ -0,0 +1,183 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2020 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * Concrete functions for libevhtp of the + * Virtual clixon restconf API functions. + * @see restconf_api.h for virtual API + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* evhtp */ +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include + +#include "restconf_lib.h" +#include "restconf_api.h" /* Virtual api */ + + +/*! Add HTTP header field name and value to reply, evhtp specific + * @param[in] req Evhtp http request handle + * @param[in] code HTTP status code + * @see eg RFC 7230 + */ +int +restconf_reply_status_code(void *req0, + int code) +{ + evhtp_request_t *req = (evhtp_request_t *)req0; + + req->status = code; + return 0; +} + +/*! Add HTTP header field name and value to reply, evhtp specific + * @param[in] req Evhtp http request handle + * @param[in] name HTTP header field name + * @param[in] vfmt HTTP header field value format string w variable parameter + * @see eg RFC 7230 + */ +int +restconf_reply_header_add(void *req0, + char *name, + char *vfmt, + ...) + +{ + evhtp_request_t *req = (evhtp_request_t *)req0; + int retval = -1; + size_t vlen; + char *value = NULL; + va_list ap; + evhtp_header_t *evhdr; + + if (req == NULL || name == NULL || vfmt == NULL){ + clicon_err(OE_CFG, EINVAL, "req, name or value is NULL"); + return -1; + } + va_start(ap, vfmt); + vlen = vsnprintf(NULL, 0, vfmt, ap); + va_end(ap); + /* allocate value string exactly fitting */ + if ((value = malloc(vlen+1)) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + /* second round: compute actual value */ + va_start(ap, vfmt); + if (vsnprintf(value, vlen+1, vfmt, ap) < 0){ + clicon_err(OE_UNIX, errno, "vsnprintf"); + va_end(ap); + goto done; + } + va_end(ap); + if ((evhdr = evhtp_header_new(name, value, 0, 1)) == NULL){ /* 1: free after use */ + clicon_err(OE_CFG, errno, "evhttp_header_new"); + goto done; + } + value = NULL; /* freed by evhtp */ + evhtp_headers_add_header(req->headers_out, evhdr); + retval = 0; + done: + if (value) + free(value); + return retval; +} + +/*! Send HTTP reply with potential message body + * @param[in] req Evhtp http request handle + * @param[in] cb Body as a cbuf, send if + * + * Prerequisites: status code set, headers given, body if wanted set + */ +int +restconf_reply_send(void *req0, + cbuf *cb) +{ + evhtp_request_t *req = (evhtp_request_t *)req0; + int retval = -1; + evhtp_connection_t *conn; + struct evbuffer *eb = NULL; + +#if 1 /* Optional? */ + if ((conn = evhtp_request_get_connection(req)) == NULL){ + clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection"); + goto done; + } + htp_sslutil_add_xheaders(req->headers_out, conn->ssl, HTP_SSLUTILS_XHDR_ALL); +#endif + + /* create evbuffer* : bufferevent_write_buffer/ drain, + ie send everything , except body */ + evhtp_send_reply_start(req, req->status); + + /* Write a body if cbuf is nonzero */ + if (cb != NULL && cbuf_len(cb)){ + /* Suboptimal, copy from cbuf to evbuffer */ + if ((eb = evbuffer_new()) == NULL){ + clicon_err(OE_CFG, errno, "evbuffer_new"); + goto done; + } + if (evbuffer_add(eb, cbuf_get(cb), cbuf_len(cb)) < 0){ + clicon_err(OE_CFG, errno, "evbuffer_add"); + goto done; + } + evhtp_send_reply_body(req, eb); /* conn->bev = eb, body is different */ + } + evhtp_send_reply_end(req); /* just flag finished */ + retval = 0; + done: + if (eb) + evhtp_safe_free(eb, evbuffer_free); + return retval; +} diff --git a/apps/restconf/restconf_api_fcgi.c b/apps/restconf/restconf_api_fcgi.c new file mode 100644 index 00000000..ef5be280 --- /dev/null +++ b/apps/restconf/restconf_api_fcgi.c @@ -0,0 +1,217 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2020 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * Concrete functions for FCGI of the + * Virtual clixon restconf API functions. + * @see restconf_api.h for virtual API + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include + +#include /* Need to be after clixon_xml-h due to attribute format */ + +#include "restconf_lib.h" +#include "restconf_api.h" /* Virtual api */ + +/*! Add HTTP header field name and value to reply, fcgi specific + * @param[in] req Fastcgi request handle + * @param[in] code HTTP status code + * @see eg RFC 7230 + */ +int +restconf_reply_status_code(void *req0, + int code) +{ + FCGX_Request *req = (FCGX_Request *)req0; + + FCGX_SetExitStatus(code, req->out); + return 0; +} + +/*! HTTP headers done, if there is a message body coming next + * @param[in] req Fastcgi request handle + * @retval body Handle for body handling (in fcgi same as req) + * + * HTTP-message = start-line *( header-field CRLF ) CRLF [ message-body ] + * @see eg RFC 7230 + * XXX may be unecessary (or body start or something) + */ +FCGX_Request * +restconf_reply_body_start(void *req0) +{ + FCGX_Request *req = (FCGX_Request *)req0; + + FCGX_FPrintF(req->out, "\r\n"); + return req; +} + +/*! Add HTTP header field name and value to reply, fcgi specific + * @param[in] req Fastcgi request handle + * @param[in] name HTTP header field name + * @param[in] vfmt HTTP header field value format string w variable parameter + * @see eg RFC 7230 + */ +int +restconf_reply_header_add(void *req0, + char *name, + char *vfmt, + ...) + +{ + FCGX_Request *req = (FCGX_Request *)req0; + int retval = -1; + size_t vlen; + char *value = NULL; + va_list ap; + + if (req == NULL || name == NULL || vfmt == NULL){ + clicon_err(OE_CFG, EINVAL, "req, name or value is NULL"); + return -1; + } + va_start(ap, vfmt); + vlen = vsnprintf(NULL, 0, vfmt, ap); + va_end(ap); + /* allocate value string exactly fitting */ + if ((value = malloc(vlen+1)) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + /* second round: compute actual value */ + va_start(ap, vfmt); + if (vsnprintf(value, vlen+1, vfmt, ap) < 0){ + clicon_err(OE_UNIX, errno, "vsnprintf"); + va_end(ap); + goto done; + } + va_end(ap); + FCGX_FPrintF(req->out, "%s: %s\r\n", name, value); + retval = 0; + done: + if (value) + free(value); + return retval; +} + +/*! Add HTTP message body to reply, fcgi specific + * @param[in] req Fastcgi request handle + * @param[in,out] content_len This is for Content-Length header + * @param[in] bfmt HTTP message body format string w variable parameter + * @see eg RFC 7230 + */ +int +restconf_reply_body_add(void *req0, + size_t *content_len, + char *bfmt, + ...) +{ + FCGX_Request *req = (FCGX_Request *)req0; + int retval = -1; + size_t sz; + size_t blen; + char *body = NULL; + va_list ap; + + if (req == NULL || bfmt == NULL){ + clicon_err(OE_CFG, EINVAL, "req or body is NULL"); + return -1; + } + va_start(ap, bfmt); + blen = vsnprintf(NULL, 0, bfmt, ap); + va_end(ap); + /* allocate body string exactly fitting */ + if ((body = malloc(blen+1)) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + /* second round: compute actual body */ + va_start(ap, bfmt); + if (vsnprintf(body, blen+1, bfmt, ap) < 0){ + clicon_err(OE_UNIX, errno, "vsnprintf"); + va_end(ap); + goto done; + } + va_end(ap); + FCGX_FPrintF(req->out, "%s", body); + /* Increment in/out Content-Length parameter */ + if (content_len){ + sz = strlen(body); + *content_len += sz; + } + retval = 0; + done: + if (body) + free(body); + return retval; +} + +/*! Send HTTP reply with potential message body + * @param[in] req Fastcgi request handle + * @param[in] cb Body as a cbuf, send if + * + * Prerequisites: status code set, headers given, body if wanted set + */ +int +restconf_reply_send(void *req0, + cbuf *cb) +{ + FCGX_Request *req = (FCGX_Request *)req0; + int retval = -1; + + /* Write a body if cbuf is nonzero */ + if (cb != NULL && cbuf_len(cb)){ + FCGX_FPrintF(req->out, "\r\n"); + FCGX_FPrintF(req->out, "%s", cbuf_get(cb)); + } + retval = 0; + return retval; +} diff --git a/apps/restconf/restconf_evhtp_main.c b/apps/restconf/restconf_evhtp_main.c index da646841..2cdcd7e1 100644 --- a/apps/restconf/restconf_evhtp_main.c +++ b/apps/restconf/restconf_evhtp_main.c @@ -31,7 +31,8 @@ the terms of any one of the Apache License version 2 or the GPL. ***** END LICENSE BLOCK ***** - + + * libevhtp code */ /* XXX temp constant should go away, */ @@ -43,7 +44,7 @@ /* compilation withotu threading support * XXX: could be disabled already in configure? */ -#define EVHTP_DISABLE_EVTHR +//#define EVHTP_DISABLE_EVTHR #define EVHTP_DISABLE_REGEX #include @@ -73,13 +74,9 @@ /* restconf */ -#include "restconf_lib.h" -#if 0 /* These are all dependent on FCGX */ -#include "restconf_methods.h" -#include "restconf_methods_get.h" -#include "restconf_methods_post.h" -#include "restconf_stream.h" -#endif +#include "restconf_lib.h" /* generic shared with plugins */ +#include "restconf_api.h" /* generic not shared with plugins */ +#include "restconf_root.h" /* Command line options to be passed to getopt(3) */ #define RESTCONF_OPTS "hD:f:l:p:d:y:a:u:o:P:c:k:" @@ -122,6 +119,178 @@ restconf_sig_child(int arg) } } +static char* +evhtp_method2str(enum htp_method m) +{ + switch (m){ + case htp_method_GET: + return "GET"; + break; + case htp_method_HEAD: + return "HEAD"; + break; + case htp_method_POST: + return "POST"; + break; + case htp_method_PUT: + return "PUT"; + break; + case htp_method_DELETE: + return "DELETE"; + break; + case htp_method_PATCH: + return "PATCH"; + break; + default: + return "XXX"; + break; + } +} + +static int +query_iterator(evhtp_header_t *hdr, + void *arg) +{ + cvec *qvec = (cvec *)arg; + char *key; + char *val; + char *valu = NULL; /* unescaped value */ + cg_var *cv; + + key = hdr->key; + val = hdr->val; + if (uri_percent_decode(val, &valu) < 0) + return -1; + if ((cv = cvec_add(qvec, CGV_STRING)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_add"); + return -1; + } + cv_name_set(cv, key); + cv_string_set(cv, valu); + if (valu) + free(valu); + return 0; +} + +/*! Map from evhtp information to "fcgi" type parameters used in clixon code + * + * While all these params come via one call in fcgi, the information must be taken from + * several different places in evhtp + * @param[in] h Clicon handle + * @param[in] req Evhtp request struct + * @retval 0 OK + * @retval -1 Error + * The following parameters are set: + * QUERY_STRING + * REQUEST_METHOD + * REQUEST_URI + * HTTPS + * HTTP_HOST + * HTTP_ACCEPT + * HTTP_CONTENT_TYPE + * @note there may be more used by an application plugin + */ +static int +evhtp_params_set(clicon_handle h, + evhtp_request_t *req, + cvec *qvec) +{ + int retval = -1; + htp_method meth; + evhtp_uri_t *uri; + evhtp_path_t *path; + evhtp_header_t *hdr; + + if ((uri = req->uri) == NULL){ + clicon_err(OE_DAEMON, EFAULT, "No uri"); + goto done; + } + if ((path = uri->path) == NULL){ + clicon_err(OE_DAEMON, EFAULT, "No path"); + goto done; + } + meth = evhtp_request_get_method(req); + + /* QUERY_STRING */ + if (qvec && uri->query) + if (evhtp_kvs_for_each(uri->query, query_iterator, qvec) < 0){ + clicon_err(OE_CFG, errno, "evhtp_kvs_for_each"); + goto done; + } + + if (clixon_restconf_param_set(h, "REQUEST_METHOD", evhtp_method2str(meth)) < 0) + goto done; + if (clixon_restconf_param_set(h, "REQUEST_URI", path->full) < 0) + goto done; + if (clixon_restconf_param_set(h, "HTTPS", "https") < 0) /* some string or NULL */ + goto done; + if ((hdr = evhtp_headers_find_header(req->headers_in, "Host")) != NULL){ + if (clixon_restconf_param_set(h, "HTTP_HOST", hdr->val) < 0) + goto done; + } + if ((hdr = evhtp_headers_find_header(req->headers_in, "Accept")) != NULL){ + if (clixon_restconf_param_set(h, "HTTP_ACCEPT", hdr->val) < 0) + goto done; + } + if ((hdr = evhtp_headers_find_header(req->headers_in, "Content-Type")) != NULL){ + if (clixon_restconf_param_set(h, "HTTP_CONTENT_TYPE", hdr->val) < 0) + goto done; + } + retval = 0; + done: + return retval; +} + +static int +evhtp_params_clear(clicon_handle h) +{ + int retval = -1; + char *params[] = {"QUERY_STRING", "REQUEST_METHOD", "REQUEST_URI", + "HTTPS", "HTTP_HOST", "HTTP_ACCEPT", "HTTP_CONTENT_TYPE", NULL}; + char *param; + int i=0; + + while((param=params[i]) != NULL) + if (clixon_restconf_param_del(h, param) < 0) + goto done; + retval = 0; + done: + return retval; +} + +static int +print_header(evhtp_header_t *header, + void *arg) +{ + // clicon_handle h = (clicon_handle)arg; + + clicon_debug(1, "%s %s %s", + __FUNCTION__, header->key, header->val); + return 0; +} + +static evhtp_res +cx_pre_accept(evhtp_connection_t *conn, + void *arg) +{ + // clicon_handle h = (clicon_handle)arg; + + clicon_debug(1, "%s", __FUNCTION__); + return EVHTP_RES_OK; +} + +static evhtp_res +cx_post_accept(evhtp_connection_t *conn, + void *arg) +{ + // clicon_handle h = (clicon_handle)arg; + + clicon_debug(1, "%s", __FUNCTION__); + return EVHTP_RES_OK; +} + +/*! Generic callback called if no other callbacks are matched + */ static void cx_gencb(evhtp_request_t *req, void *arg) @@ -129,7 +298,7 @@ cx_gencb(evhtp_request_t *req, evhtp_connection_t *conn; // clicon_handle h = arg; - fprintf(stderr, "%s\n", __FUNCTION__); + clicon_debug(1, "%s", __FUNCTION__); if (req == NULL){ errno = EINVAL; return; @@ -145,67 +314,105 @@ cx_gencb(evhtp_request_t *req, return; /* void */ } -static evhtp_res -cx_pre_accept(evhtp_connection_t *conn, - void *arg) +/*! /.well-known callback + * @see cx_genb + */ +static void +cx_path_wellknown(evhtp_request_t *req, + void *arg) { - fprintf(stderr, "%s\n", __FUNCTION__); - return EVHTP_RES_OK; + clicon_handle h = arg; + + /* input debug */ + if (clicon_debug_get()) + evhtp_headers_for_each(req->headers_in, print_header, h); + /* get accepted connection */ + + /* set fcgi-like paramaters (ignore query vector) */ + if (evhtp_params_set(h, req, NULL) < 0) + goto done; + /* call generic function */ + if (api_well_known(h, req) < 0) + goto done; + + /* Clear (fcgi) paramaters from this request */ + if (evhtp_params_clear(h) < 0) + goto done; + done: + return; /* void */ } -static evhtp_res -cx_post_accept(evhtp_connection_t *conn, - void *arg) -{ - fprintf(stderr, "%s\n", __FUNCTION__); - return EVHTP_RES_OK; -} - -static int -print_header_(evhtp_header_t * header, void * arg) { - fprintf(stderr, "%s: %s\n", header->key, header->val); - return 0; -} - -/*! Generic callback called if no other callbacks are matched +/*! /restconf callback + * @see cx_genb */ static void cx_path_restconf(evhtp_request_t *req, void *arg) { evhtp_connection_t *conn; - // clicon_handle h = arg; + clicon_handle h = arg; struct evbuffer *b = NULL; - htp_method meth; + cvec *qvec = NULL; + size_t len = 0; + cbuf *cblen = NULL; - fprintf(stderr, "%s\n", __FUNCTION__); + clicon_debug(1, "%s", __FUNCTION__); if (req == NULL){ errno = EINVAL; goto done; } - if ((conn = evhtp_request_get_connection(req)) == NULL) - goto done; - meth = evhtp_request_get_method(req); - fprintf(stderr, "%s method:%d\n", __FUNCTION__, meth); - evhtp_headers_for_each(req->headers_in, print_header_, NULL); + /* input debug */ + if (clicon_debug_get()) + evhtp_headers_for_each(req->headers_in, print_header, h); - if ((b = evbuffer_new()) == NULL){ + if ((cblen = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } - htp_sslutil_add_xheaders( - req->headers_out, - conn->ssl, - HTP_SSLUTILS_XHDR_ALL); + /* Query vector, ie the ?a=x&b=y stuff */ + if ((qvec = cvec_new(0)) ==NULL){ + clicon_err(OE_UNIX, errno, "cvec_new"); + goto done; + } + /* get accepted connection */ + if ((conn = evhtp_request_get_connection(req)) == NULL){ + clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection"); + goto done; + } + /* Get all parameters from this request (resembling fcgi) */ + if (evhtp_params_set(h, req, qvec) < 0) + goto done; + + /* 1. create body */ + if ((b = evbuffer_new()) == NULL){ + clicon_err(OE_DAEMON, errno, "evbuffer_new"); + goto done; + } + cprintf(cblen, "%lu", len); + + /* 2. add headers (can mix with body) */ + evhtp_headers_add_header(req->headers_out, evhtp_header_new("Cache-Control", "no-cache", 0, 0)); + evhtp_headers_add_header(req->headers_out, evhtp_header_new("Content-Type", "application/xrd+xml", 0, 0)); + evhtp_headers_add_header(req->headers_out, evhtp_header_new("Content-Length", cbuf_get(cblen), 0, 0)); + + /* Optional? */ + htp_sslutil_add_xheaders(req->headers_out, conn->ssl, HTP_SSLUTILS_XHDR_ALL); + + /* 3. send reply */ evhtp_send_reply_start(req, EVHTP_RES_OK); - evbuffer_add(b, "hej\n", strlen("hej\n\n")); evhtp_send_reply_body(req, b); evhtp_send_reply_end(req); - // evhtp_headers_add_header(request->headers_out, evhtp_header_new("Host", "localhost", 0, 0)); evhtp_headers_add_headers(request->headers_out, headers); - - - + /* Clear (fcgi)paramaters */ + if (evhtp_params_clear(h) < 0) + goto done; done: + if (qvec) + cvec_free(qvec); + if (cblen) + cbuf_free(cblen); + if (b) + evhtp_safe_free(b, evbuffer_free); return; /* void */ } @@ -246,26 +453,27 @@ int main(int argc, char **argv) { - int retval = -1; - char *argv0 = argv[0]; - int c; - clicon_handle h; - char *dir; - int logdst = CLICON_LOG_SYSLOG; - yang_stmt *yspec = NULL; - char *str; - clixon_plugin *cp = NULL; - cvec *nsctx_global = NULL; /* Global namespace context */ - size_t cligen_buflen; - size_t cligen_bufthreshold; - uint16_t port = 443; + int retval = -1; + char *argv0 = argv[0]; + int c; + clicon_handle h; + char *dir; + int logdst = CLICON_LOG_SYSLOG; + yang_stmt *yspec = NULL; + char *str; + clixon_plugin *cp = NULL; + cvec *nsctx_global = NULL; /* Global namespace context */ + size_t cligen_buflen; + size_t cligen_bufthreshold; + uint16_t port = 443; #ifdef _EVHTP_NYI - char *stream_path; + char *stream_path; #endif evhtp_t *htp = NULL; struct event_base *evbase = NULL; evhtp_ssl_cfg_t *ssl_config = NULL; struct stat f_stat; + int dbg = 0; /* In the startup, logs to stderr & debug flag set later */ clicon_log_init(__PROGRAM__, LOG_INFO, logdst); @@ -282,7 +490,7 @@ main(int argc, usage(h, argv0); break; case 'D' : /* debug */ - if (sscanf(optarg, "%d", &debug) != 1) + if (sscanf(optarg, "%d", &dbg) != 1) usage(h, argv0); break; case 'f': /* override config file */ @@ -303,9 +511,9 @@ main(int argc, /* * Logs, error and debug to stderr or syslog, set debug level */ - clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, logdst); + clicon_log_init(__PROGRAM__, dbg?LOG_DEBUG:LOG_INFO, logdst); - clicon_debug_init(debug, NULL); + clicon_debug_init(dbg, NULL); clicon_log(LOG_NOTICE, "%s: %u Started", __PROGRAM__, getpid()); if (set_signal(SIGTERM, restconf_sig_term, NULL) < 0){ clicon_err(OE_DAEMON, errno, "Setting signal"); @@ -390,7 +598,6 @@ main(int argc, } argc -= optind; argv += optind; - /* Check ssl mandatory options */ if (ssl_config->pemfile == NULL || ssl_config->privfile == NULL) usage(h, argv0); @@ -412,6 +619,53 @@ main(int argc, /* Access the remaining argv/argc options (after --) w clicon-argv_get() */ clicon_argv_set(h, argv0, argc, argv); + /* Init evhtp */ + if ((evbase = event_base_new()) == NULL){ + clicon_err(OE_UNIX, errno, "event_base_new"); + goto done; + } + /* create a new evhtp_t instance */ + if ((htp = evhtp_new(evbase, NULL)) == NULL){ + clicon_err(OE_UNIX, errno, "evhtp_new"); + goto done; + } + if (evhtp_ssl_init(htp, 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); + + /* bind to a socket, optionally with specific protocol support formatting + * If port is proteced must be done as root? + */ + if (evhtp_bind_socket(htp, "127.0.0.1", port, 128) < 0){ + clicon_err(OE_UNIX, errno, "evhtp_bind_socket"); + goto done; + } + if (restconf_drop_privileges(h, WWWUSER) < 0) + goto done; + /* Init cligen buffers */ cligen_buflen = clicon_option_int(h, "CLICON_CLI_BUF_START"); cligen_bufthreshold = clicon_option_int(h, "CLICON_CLI_BUF_THRESHOLD"); @@ -422,7 +676,6 @@ main(int argc, */ if (netconf_module_features(h) < 0) goto done; - /* Create top-level yang spec and store as option */ if ((yspec = yspec_new()) == NULL) goto done; @@ -491,8 +744,8 @@ main(int argc, goto done; /* Dump configuration options on debug */ - if (debug) - clicon_option_dump(h, debug); + if (dbg) + clicon_option_dump(h, dbg); /* Call start function in all plugins before we go interactive */ @@ -503,40 +756,6 @@ main(int argc, if (clicon_options_main(h) < 0) goto done; - /* Init evhtp */ - if ((evbase = event_base_new()) == NULL){ - clicon_err(OE_UNIX, errno, "event_base_new"); - goto done; - } - /* create a new evhtp_t instance */ - if ((htp = evhtp_new(evbase, NULL)) == NULL){ - clicon_err(OE_UNIX, errno, "evhtp_new"); - goto done; - } - if (evhtp_ssl_init(htp, ssl_config) < 0){ - clicon_err(OE_UNIX, errno, "evhtp_new"); - goto done; - } - /* Generic callback called if no other callbacks are matched */ - evhtp_set_gencb(htp, cx_gencb, h); - - /* 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 on a specific path */ - if (evhtp_set_cb(htp, "/" RESTCONF_API, cx_path_restconf, h) == NULL){ - clicon_err(OE_EVENTS, errno, "evhtp_set_cb"); - goto done; - } - - /* bind to a socket, optionally with specific protocol support formatting */ - if (evhtp_bind_socket(htp, "127.0.0.1", port, 128) < 0){ - clicon_err(OE_UNIX, errno, "evhtp_bind_socket"); - goto done; - } event_base_loop(evbase, 0); diff --git a/apps/restconf/restconf_fcgi_lib.c b/apps/restconf/restconf_fcgi_lib.c index 1ad9678c..5ed0d86b 100644 --- a/apps/restconf/restconf_fcgi_lib.c +++ b/apps/restconf/restconf_fcgi_lib.c @@ -194,7 +194,7 @@ restconf_internal_server_error(clicon_handle h, FCGX_Request *r) { char *path; - + clicon_debug(1, "%s", __FUNCTION__); path = clixon_restconf_param_get(h, "REQUEST_URI"); FCGX_FPrintF(r->out, "Status: 500 Internal Server Error\r\n"); /* 500 internal server error */ @@ -478,4 +478,3 @@ http_location(clicon_handle h, return retval; } - diff --git a/apps/restconf/restconf_fcgi_lib.h b/apps/restconf/restconf_fcgi_lib.h index 23142089..af7a7910 100644 --- a/apps/restconf/restconf_fcgi_lib.h +++ b/apps/restconf/restconf_fcgi_lib.h @@ -49,12 +49,12 @@ int restconf_conflict(FCGX_Request *r); int restconf_unsupported_media(FCGX_Request *r); int restconf_internal_server_error(clicon_handle h, FCGX_Request *r); int restconf_notimplemented(FCGX_Request *r); -int restconf_test(FCGX_Request *r, int dbg); -int clixon_restconf_params_set(clicon_handle h, char **envp); +int restconf_test(FCGX_Request *r, int dbg); +int clixon_restconf_params_set(clicon_handle h, + char **envp); int clixon_restconf_params_clear(clicon_handle h, char **envp); cbuf *readdata(FCGX_Request *r); -int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr, - int pretty, enum restconf_media media, int code); +int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr, int pretty, restconf_media media, int code0); int http_location(clicon_handle h, FCGX_Request *r, cxobj *xobj); #endif /* _RESTCONF_FCGI_LIB_H_ */ diff --git a/apps/restconf/restconf_fcgi_main.c b/apps/restconf/restconf_fcgi_main.c index 24f45c7c..fa4422be 100644 --- a/apps/restconf/restconf_fcgi_main.c +++ b/apps/restconf/restconf_fcgi_main.c @@ -77,9 +77,12 @@ #include /* Need to be after clixon_xml.h due to attribute format */ /* restconf */ -#include "restconf_lib.h" -#include "restconf_fcgi_lib.h" -#include "restconf_methods.h" +#include "restconf_lib.h" /* generic shared with plugins */ +#include "restconf_api.h" /* generic not shared with plugins */ + +#include "restconf_root.h" /* generic not shared with plugins */ +#include "restconf_fcgi_lib.h" /* fcgi specific */ +#include "restconf_methods.h" /* fcgi specific */ #include "restconf_methods_get.h" #include "restconf_methods_post.h" #include "restconf_stream.h" @@ -101,7 +104,7 @@ */ static int api_data(clicon_handle h, - FCGX_Request *r, + FCGX_Request *req, char *api_path, cvec *pcvec, int pi, @@ -117,21 +120,21 @@ api_data(clicon_handle h, request_method = clixon_restconf_param_get(h, "REQUEST_METHOD"); clicon_debug(1, "%s method:%s", __FUNCTION__, request_method); if (strcmp(request_method, "OPTIONS")==0) - retval = api_data_options(h, r); + retval = api_data_options(h, req); else if (strcmp(request_method, "HEAD")==0) - retval = api_data_head(h, r, api_path, pcvec, pi, qvec, pretty, media_out); + retval = api_data_head(h, req, api_path, pcvec, pi, qvec, pretty, media_out); else if (strcmp(request_method, "GET")==0) - retval = api_data_get(h, r, api_path, pcvec, pi, qvec, pretty, media_out); + retval = api_data_get(h, req, api_path, pcvec, pi, qvec, pretty, media_out); else if (strcmp(request_method, "POST")==0) - retval = api_data_post(h, r, api_path, pi, qvec, data, pretty, media_out); + retval = api_data_post(h, req, api_path, pi, qvec, data, pretty, media_out); else if (strcmp(request_method, "PUT")==0) - retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data, pretty, media_out); + retval = api_data_put(h, req, api_path, pcvec, pi, qvec, data, pretty, media_out); else if (strcmp(request_method, "PATCH")==0) - retval = api_data_patch(h, r, api_path, pcvec, pi, qvec, data, pretty, media_out); + retval = api_data_patch(h, req, api_path, pcvec, pi, qvec, data, pretty, media_out); else if (strcmp(request_method, "DELETE")==0) - retval = api_data_delete(h, r, api_path, pi, pretty, media_out); + retval = api_data_delete(h, req, api_path, pi, pretty, media_out); else - retval = restconf_notfound(h, r); + retval = restconf_notfound(h, req); clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); return retval; } @@ -181,27 +184,44 @@ api_operations(clicon_handle h, * In line with the best practices defined by [RFC7320], RESTCONF * enables deployments to specify where the RESTCONF API is located. */ +#if 0 static int api_well_known(clicon_handle h, - FCGX_Request *r) + FCGX_Request *req) { - clicon_debug(1, "%s", __FUNCTION__); - FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n"); - FCGX_FPrintF(r->out, "Content-Type: application/xrd+xml\r\n"); - FCGX_FPrintF(r->out, "\r\n"); - FCGX_SetExitStatus(200, r->out); /* OK */ - FCGX_FPrintF(r->out, "\n"); - FCGX_FPrintF(r->out, " \n"); - FCGX_FPrintF(r->out, "\r\n"); + char *request_method; + FCGX_Request *body; + + /* call generic function */ + if (api_well_known(h, req) < 0) + goto done; + clicon_debug(1, "%s", __FUNCTION__); + if (req == NULL){ + errno = EINVAL; + goto done; + } + request_method = clixon_restconf_param_get(h, "REQUEST_METHOD"); + if (strcmp(request_method, "GET") !=0 ) + return restconf_method_notallowed(req, "GET"); + restconf_reply_status_code(req, 200); /* OK */ + restconf_reply_header_add(req, "Cache-Control", "no-cache"); + restconf_reply_header_add(req, "Content-Type", "application/xrd+xml"); + body = restconf_reply_body_start(req); + + restconf_reply_body_add(body, NULL, "\n"); + restconf_reply_body_add(body, NULL, " \n"); + restconf_reply_body_add(body, NULL, "\r\n"); + done: return 0; } - +#endif /*! Retrieve the Top-Level API Resource * @param[in] h Clicon handle * @param[in] r Fastcgi request handle * @note Only returns null for operations and data,... * See RFC8040 3.3 + * XXX doesnt check method */ static int api_root(clicon_handle h, @@ -318,11 +338,11 @@ api_yang_library_version(clicon_handle h, */ static int api_restconf(clicon_handle h, - FCGX_Request *r) + FCGX_Request *req) { int retval = -1; char *path; - char *query; + char *query = NULL; char *method; char **pvec = NULL; int pn; @@ -357,7 +377,7 @@ api_restconf(clicon_handle h, if (strcmp(media_str, "*/*") == 0) /* catch-all */ media_out = YANG_DATA_JSON; else{ - retval = restconf_unsupported_media(r); + retval = restconf_unsupported_media(req); goto done; } } @@ -367,34 +387,35 @@ api_restconf(clicon_handle h, goto done; /* Sanity check of path. Should be /restconf/ */ if (pn < 2){ - restconf_notfound(h, r); + restconf_notfound(h, req); goto ok; } if (strlen(pvec[0]) != 0){ - retval = restconf_notfound(h, r); + retval = restconf_notfound(h, req); goto done; } if (strcmp(pvec[1], RESTCONF_API)){ - retval = restconf_notfound(h, r); + retval = restconf_notfound(h, req); goto done; } - restconf_test(r, 1); + restconf_test(req, 1); if (pn == 2){ - retval = api_root(h, r, pretty, media_out); + retval = api_root(h, req, pretty, media_out); goto done; } if ((method = pvec[2]) == NULL){ - retval = restconf_notfound(h, r); + retval = restconf_notfound(h, req); goto done; } clicon_debug(1, "%s: method=%s", __FUNCTION__, method); - if (str2cvec(query, '&', '=', &qvec) < 0) - goto done; + if (query != NULL && strlen(query)) + if (str2cvec(query, '&', '=', &qvec) < 0) + goto done; if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */ goto done; /* data */ - if ((cb = readdata(r)) == NULL) + if ((cb = readdata(req)) == NULL) goto done; data = cbuf_get(cb); clicon_debug(1, "%s DATA=%s", __FUNCTION__, data); @@ -404,7 +425,7 @@ api_restconf(clicon_handle h, /* If present, check credentials. See "plugin_credentials" in plugin * See RFC 8040 section 2.5 */ - if ((authenticated = clixon_plugin_auth_all(h, r)) < 0) + if ((authenticated = clixon_plugin_auth_all(h, req)) < 0) goto done; clicon_debug(1, "%s auth:%d %s", __FUNCTION__, authenticated, clicon_username_get(h)); @@ -417,7 +438,7 @@ api_restconf(clicon_handle h, if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0) goto done; if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xerr, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xerr, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -425,23 +446,23 @@ api_restconf(clicon_handle h, } clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h)); if (strcmp(method, "yang-library-version")==0){ - if (api_yang_library_version(h, r, pretty, media_out) < 0) + if (api_yang_library_version(h, req, pretty, media_out) < 0) goto done; } else if (strcmp(method, "data") == 0){ /* restconf, skip /api/data */ - if (api_data(h, r, path, pcvec, 2, qvec, data, + if (api_data(h, req, path, pcvec, 2, qvec, data, pretty, media_out) < 0) goto done; } else if (strcmp(method, "operations") == 0){ /* rpc */ - if (api_operations(h, r, path, pcvec, 2, qvec, data, + if (api_operations(h, req, path, pcvec, 2, qvec, data, pretty, media_out) < 0) goto done; } else if (strcmp(method, "test") == 0) - restconf_test(r, 0); + restconf_test(req, 0); else - restconf_notfound(h, r); + restconf_notfound(h, req); ok: retval = 0; done: @@ -534,7 +555,7 @@ main(int argc, int sock; char *argv0 = argv[0]; FCGX_Request request; - FCGX_Request *r = &request; + FCGX_Request *req = &request; int c; char *sockpath; char *path; @@ -664,6 +685,7 @@ main(int argc, cligen_bufthreshold = clicon_option_int(h, "CLICON_CLI_BUF_THRESHOLD"); cbuf_alloc_set(cligen_buflen, cligen_bufthreshold); + /* Add (hardcoded) netconf features in case ietf-netconf loaded here * Otherwise it is loaded in netconf_module_load below */ @@ -759,7 +781,21 @@ main(int argc, clicon_err(OE_CFG, errno, "FCGX_OpenSocket"); goto done; } - +#if 1 + { + /* Change group of fcgi sock fronting reverse proxy to WWWUSER, the effective group is clicon + * which is backend. */ + gid_t wgid = -1; + if (group_name2gid(WWWUSER, &wgid) < 0){ + clicon_log(LOG_ERR, "'%s' does not seem to be a valid user group.", WWWUSER); + goto done; + } + if (chown(sockpath, -1, wgid) < 0){ + clicon_err(OE_CFG, errno, "chown"); + goto done; + } + } +#endif if (clicon_socket_set(h, sock) < 0) goto done; /* umask settings may interfer: we want group to write: this is 774 */ @@ -767,14 +803,18 @@ main(int argc, clicon_err(OE_UNIX, errno, "chmod"); goto done; } - if (FCGX_InitRequest(r, sock, 0) != 0){ +#if 1 + if (restconf_drop_privileges(h, WWWUSER) < 0) + goto done; +#endif + if (FCGX_InitRequest(req, sock, 0) != 0){ clicon_err(OE_CFG, errno, "FCGX_InitRequest"); goto done; } while (1) { finish = 1; /* If zero, dont finish request, initiate new */ - if (FCGX_Accept_r(r) < 0) { + if (FCGX_Accept_r(req) < 0) { clicon_err(OE_CFG, errno, "FCGX_Accept_r"); goto done; } @@ -794,32 +834,32 @@ main(int argc, /* Translate from FCGI parameter form to Clixon runtime data * XXX: potential name collision? */ - if (clixon_restconf_params_set(h, r->envp) < 0) + if (clixon_restconf_params_set(h, req->envp) < 0) goto done; if ((path = clixon_restconf_param_get(h, "REQUEST_URI")) != NULL){ clicon_debug(1, "path: %s", path); if (strncmp(path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0) - api_restconf(h, r); /* This is the function */ + api_restconf(h, req); /* This is the function */ else if (strncmp(path+1, stream_path, strlen(stream_path)) == 0) { - api_stream(h, r, stream_path, &finish); + api_stream(h, req, stream_path, &finish); } else if (strncmp(path, RESTCONF_WELL_KNOWN, strlen(RESTCONF_WELL_KNOWN)) == 0) { - api_well_known(h, r); /* */ + api_well_known(h, req); /* */ } else{ clicon_debug(1, "top-level %s not found", path); - restconf_notfound(h, r); + restconf_notfound(h, req); } } else clicon_debug(1, "NULL URI"); - if (clixon_restconf_params_clear(h, r->envp) < 0) + if (clixon_restconf_params_clear(h, req->envp) < 0) goto done; if (finish) - FCGX_Finish_r(r); + FCGX_Finish_r(req); else{ /* A handler is forked so we initiate a new request after instead of finnishing the old */ - if (FCGX_InitRequest(r, sock, 0) != 0){ + if (FCGX_InitRequest(req, sock, 0) != 0){ clicon_err(OE_CFG, errno, "FCGX_InitRequest"); goto done; } diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index a361f581..750be387 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -59,6 +59,7 @@ /* clicon */ #include +#include "restconf_api.h" #include "restconf_lib.h" /* See RFC 8040 Section 7: Mapping from NETCONF to Status Code @@ -443,6 +444,13 @@ clixon_restconf_param_set(clicon_handle h, return clicon_data_set(h, param, val); } +/*! Delete restconf http parameter + * @param[in] h Clicon handle + * @param[in] name Data name + * @retval 0 OK + * @retval -1 Error + * Currently using clixon runtime data but there is risk for colliding names + */ int clixon_restconf_param_del(clicon_handle h, char *param) @@ -469,3 +477,78 @@ restconf_uripath(clicon_handle h) *q = '\0'; return path; } + + +/*! Drop privileges from root to user (or already at user) + * @param[in] h Clicon handle + * @param[in] user Drop to this level + * Group set to clicon to communicate with backend + */ +int +restconf_drop_privileges(clicon_handle h, + char *user) +{ + int retval = -1; + uid_t newuid = -1; + uid_t uid; + char *group; + gid_t gid = -1; + + clicon_debug(1, "%s", __FUNCTION__); + /* Sanity check: backend group exists */ + if ((group = clicon_sock_group(h)) == NULL){ + clicon_err(OE_FATAL, 0, "clicon_sock_group option not set"); + return -1; + } + if (group_name2gid(group, &gid) < 0){ + clicon_log(LOG_ERR, "'%s' does not seem to be a valid user group.\n" /* \n required here due to multi-line log */ + "The config demon requires a valid group to create a server UNIX socket\n" + "Define a valid CLICON_SOCK_GROUP in %s or via the -g option\n" + "or create the group and add the user to it. Check documentation for how to do this on your platform", + group, + clicon_configfile(h)); + goto done; + } + /* Get (wanted) new www user id */ + if (name2uid(user, &newuid) < 0){ + clicon_err(OE_DAEMON, errno, "'%s' is not a valid user .\n", user); + goto done; + } + /* get current backend userid, if already at this level OK */ + if ((uid = getuid()) == newuid) + goto ok; + if (uid != 0){ + clicon_err(OE_DAEMON, EPERM, "Privileges can only be dropped from root user (uid is %u)\n", uid); + goto done; + } + if (setgid(gid) == -1) { + clicon_err(OE_DAEMON, errno, "setgid %d", gid); + goto done; + } + if (drop_priv_perm(newuid) < 0) + goto done; + /* Verify you cannot regain root privileges */ + if (setuid(0) != -1){ + clicon_err(OE_DAEMON, EPERM, "Could regain root privilieges"); + goto done; + } + clicon_debug(1, "%s dropped privileges from root to %s(%d)", + __FUNCTION__, user, newuid); + ok: + retval = 0; + done: + return retval; +} + +/*! HTTP error 405 + * @param[in] req Generic Www handle + * @param[in] allow Which methods are allowed + */ +int +restconf_method_notallowed(void *req, + char *allow) +{ + restconf_reply_status_code(req, 405); + restconf_reply_header_add(req, "Allow", "%s", allow); + return 0; +} diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index fa9f56bf..5b570c34 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -38,21 +38,8 @@ #define _RESTCONF_LIB_H_ /* - * Constants + * Types */ -#define RESTCONF_API "restconf" - -/* RESTCONF enables deployments to specify where the RESTCONF API is - located. The client discovers this by getting the "/.well-known/host-meta" - resource -*/ -#define RESTCONF_WELL_KNOWN "/.well-known/host-meta" - - -/* - * Variables - */ - /*! RESTCONF media types * @see http_media_map * (also in clixon_restconf.h) @@ -81,5 +68,7 @@ char *clixon_restconf_param_get(clicon_handle h, char *param); int clixon_restconf_param_set(clicon_handle h, char *param, char *val); int clixon_restconf_param_del(clicon_handle h, char *param); char *restconf_uripath(clicon_handle h); +int restconf_drop_privileges(clicon_handle h, char *user); +int restconf_method_notallowed(void *req, char *allow); #endif /* _RESTCONF_LIB_H_ */ diff --git a/apps/restconf/restconf_root.c b/apps/restconf/restconf_root.c new file mode 100644 index 00000000..56640608 --- /dev/null +++ b/apps/restconf/restconf_root.c @@ -0,0 +1,118 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + * Generic restconf root handlers eg for /restconf /.well-known, etc + */ + + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* chmod */ + +/* cligen */ +#include + +/* clicon */ +#include + +/* restconf */ +#include "restconf_lib.h" +#include "restconf_api.h" +#include "restconf_root.h" + + +/*! Determine the root of the RESTCONF API + * @param[in] h Clicon handle + * @param[in] req Generic Www handle (can be part of clixon handle) + * @param[in] cb Body buffer + * @see RFC8040 3.1 and RFC7320 + * In line with the best practices defined by [RFC7320], RESTCONF + * enables deployments to specify where the RESTCONF API is located. + */ +int +api_well_known(clicon_handle h, + void *req) +{ + int retval = -1; + char *request_method; + cbuf *cb = NULL; + + clicon_debug(1, "%s", __FUNCTION__); + if (req == NULL){ + errno = EINVAL; + goto done; + } + request_method = clixon_restconf_param_get(h, "REQUEST_METHOD"); + if (strcmp(request_method, "GET") != 0){ + restconf_method_notallowed(req, "GET"); + goto ok; + } + restconf_reply_status_code(req, 200); /* OK */ + restconf_reply_header_add(req, "Cache-Control", "no-cache"); + restconf_reply_header_add(req, "Content-Type", "application/xrd+xml"); + /* Create body */ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "\n"); + cprintf(cb, " \n"); + cprintf(cb, "\r\n"); + + /* Must be after body */ + restconf_reply_header_add(req, "Content-Length", "%d", cbuf_len(cb)); + if (restconf_reply_send(req, cb) < 0) + goto done; + ok: + retval = 0; + done: + if (cb) + cbuf_free(cb); + return retval; +} + diff --git a/apps/restconf/restconf_root.h b/apps/restconf/restconf_root.h new file mode 100644 index 00000000..d7a06160 --- /dev/null +++ b/apps/restconf/restconf_root.h @@ -0,0 +1,57 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + * + * Generic restconf root handlers eg for /restconf /.well-known, etc + */ + +#ifndef _RESTCONF_ROOT_H_ +#define _RESTCONF_ROOT_H_ + +/* + * Constants + */ +#define RESTCONF_API "restconf" + +/* RESTCONF enables deployments to specify where the RESTCONF API is + located. The client discovers this by getting the "/.well-known/host-meta" + resource +*/ +#define RESTCONF_WELL_KNOWN "/.well-known/host-meta" + +/* + * Prototypes + */ +int api_well_known(clicon_handle h, void *req); + +#endif /* _RESTCONF_ROOT_H_ */ diff --git a/configure.ac b/configure.ac index a596e841..11c5a897 100644 --- a/configure.ac +++ b/configure.ac @@ -235,6 +235,7 @@ AC_ARG_WITH([restconf], # Common actions for all restconf packages if test "x${with_restconf}" != "x"; then # This is for changing web user default www-data + # Should this be a runtime option? AC_ARG_WITH([wwwuser], [AS_HELP_STRING([--with-wwwuser=],[Set www user different from www-data])]) if test "${with_wwwuser}"; then diff --git a/test/lib.sh b/test/lib.sh index a31d8336..fc8b5438 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -90,9 +90,6 @@ fi # RESTCONF protocol, eg http or https : ${RCPROTO:=http} -# RESTCONF port -: ${RCPORT:=80} - # RESTCONF error message (if not up) : ${RCERROR:="HTTP/1.1 502 Bad Gateway"} @@ -229,8 +226,11 @@ wait_backend(){ done } +# Start restconf daemon +# @see wait_restconf start_restconf(){ # Start in background +# echo "sudo -u $wwwuser -s $clixon_restconf $RCLOG -D $DBG $*" sudo -u $wwwuser -s $clixon_restconf $RCLOG -D $DBG $* & if [ $? -ne 0 ]; then err @@ -246,12 +246,16 @@ stop_restconf(){ } # Wait for restconf to stop sending 502 Bad Gateway +# @see start_restconf wait_restconf(){ - hdr=$(curl --head -sS $RCPROTO://localhost:$RCPORT/restconf) +# echo "curl -kis $RCPROTO://localhost/restconf" + hdr=$(curl -kis $RCPROTO://localhost/restconf) +# echo "hdr:\"$hdr\"" let i=0; - while [[ $hdr == "$RCERROR"* ]]; do + while [[ $hdr != *"200 OK"* ]]; do sleep 1 - hdr=$(curl --head -sS http://localhost/restconf) + hdr=$(curl -kis $RCPROTO://localhost/restconf) +# echo "hdr:\"$hdr\"" let i++; # echo "wait_restconf $i" if [ $i -ge $RCWAIT ]; then diff --git a/test/test_restconf.sh b/test/test_restconf.sh index e0500dbc..72d81247 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -57,26 +57,26 @@ if [ $RC -ne 0 ]; then new "start restconf daemon" start_restconf -f $cfg - new "waiting" - wait_restconf fi +new "waiting" +wait_restconf new "restconf root discovery. RFC 8040 3.1 (xml+xrd)" -expectpart "$(curl -si -X GET http://localhost/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "" "" "" +expectpart "$(curl -sik -X GET $RCPROTO://localhost/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "" "" "" new "restconf get restconf resource. RFC 8040 3.3 (json)" -expectpart "$(curl -si -X GET -H "Accept: application/yang-data+json" http://localhost/restconf)" 0 'HTTP/1.1 200 OK' '{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2016-06-21"}}' +expectpart "$(curl -si -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":"2016-06-21"}}' new "restconf get restconf resource. RFC 8040 3.3 (xml)" # Get XML instead of JSON? -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf)" 0 'HTTP/1.1 200 OK' '2016-06-21' +expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf)" 0 'HTTP/1.1 200 OK' '2016-06-21' # Should be alphabetically ordered new "restconf get restconf/operations. RFC8040 3.3.2 (json)" -expectpart "$(curl -si -X GET http://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\],"clixon-rfc5277:create-subscription":\[null\]}}' +expectpart "$(curl -si -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\],"clixon-rfc5277:create-subscription":\[null\]}}' new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" -ret=$(curl -s -X GET -H "Accept: application/yang-data+xml" http://localhost/restconf/operations) +ret=$(curl -s -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/operations) expect='' match=`echo $ret | grep --null -Eo "$expect"` if [ -z "$match" ]; then @@ -84,10 +84,10 @@ if [ -z "$match" ]; then fi new "restconf get restconf/yang-library-version. RFC8040 3.3.3" -expectpart "$(curl -si -X GET http://localhost/restconf/yang-library-version)" 0 'HTTP/1.1 200 OK' '{"yang-library-version":"2016-06-21"}' +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/yang-library-version)" 0 'HTTP/1.1 200 OK' '{"yang-library-version":"2016-06-21"}' new "restconf get restconf/yang-library-version. RFC8040 3.3.3 (xml)" -ret=$(curl -s -X GET -H "Accept: application/yang-data+xml" http://localhost/restconf/yang-library-version) +ret=$(curl -s -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/yang-library-version) expect="2016-06-21" match=`echo $ret | grep --null -Eo "$expect"` if [ -z "$match" ]; then @@ -95,46 +95,46 @@ if [ -z "$match" ]; then fi new "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895 (explicit resource)" -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://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 -si -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"}\]}' new "restconf options. RFC 8040 4.1" -expectpart "$(curl -is -X OPTIONS http://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE" +expectpart "$(curl -is -X OPTIONS $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE" # -I means HEAD new "restconf HEAD. RFC 8040 4.2" -expectpart "$(curl -si -I -H "Accept: application/yang-data+json" http://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+json" +expectpart "$(curl -si -I -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+json" new "restconf empty rpc JSON" -expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":null} http://localhost/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -si -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" new "restconf empty rpc XML" -expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+xml" -d '' http://localhost/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+xml" -d '' $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content" new "restconf empty rpc, default media type should fail" -expectpart "$(curl -si -X POST -d {\"clixon-example:input\":null} http://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type' +expectpart "$(curl -si -X POST -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type' new "restconf empty rpc, default media type should fail (JSON)" -expectpart "$(curl -si -X POST -H "Accept: application/yang-data+json" -d {\"clixon-example:input\":null} http://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type' +expectpart "$(curl -si -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' new "restconf empty rpc with extra args (should fail)" -expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":{\"extra\":null}} http://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 -si -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"}}}' # Irritiating to get debugs on the terminal #new "restconf debug rpc" -#expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-lib:input\":{\"level\":0}} http://localhost/restconf/operations/clixon-lib:debug)" 0 "HTTP/1.1 204 No Content" +#expectpart "$(curl -si -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" new "restconf get empty config + state json" -expecteq "$(curl -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["41","42","43"]}} +expecteq "$(curl -sS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["41","42","43"]}} ' new "restconf get empty config + state json with wrong module name" -expectpart "$(curl -siSG http://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 -siSG $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"}}}' #'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"}}}' new "restconf get empty config + state xml" -ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/clixon-example:state) +ret=$(curl -s -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state) expect='414243' match=`echo $ret | grep --null -Eo "$expect"` if [ -z "$match" ]; then @@ -142,12 +142,12 @@ if [ -z "$match" ]; then fi new "restconf get data type json" -expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"} +expecteq "$(curl -s -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"} ' new "restconf get state operation" # Cant get shell macros to work, inline matching from lib.sh -ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/clixon-example:state/op=42) +ret=$(curl -s -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42) expect='42' match=`echo $ret | grep --null -Eo "$expect"` if [ -z "$match" ]; then @@ -155,12 +155,12 @@ if [ -z "$match" ]; then fi new "restconf get state operation type json" -expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"} +expecteq "$(curl -s -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"} ' new "restconf get state operation type xml" # Cant get shell macros to work, inline matching from lib.sh -ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/clixon-example:state/op=42) +ret=$(curl -s -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42) expect='42' match=`echo $ret | grep --null -Eo "$expect"` if [ -z "$match" ]; then @@ -168,94 +168,94 @@ if [ -z "$match" ]; then fi new "restconf GET datastore" -expecteq "$(curl -s -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["41","42","43"]}} +expecteq "$(curl -s -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["41","42","43"]}} ' # Exact match new "restconf Add subtree eth/0/0 to datastore using POST" -expectpart "$(curl -s -i -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}}}' http://localhost/restconf/data)" 0 'HTTP/1.1 201 Created' 'Location: http://localhost/restconf/data/ietf-interfaces:interfaces' +expectpart "$(curl -s -i -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: http://localhost/restconf/data/ietf-interfaces:interfaces' new "restconf Re-add subtree eth/0/0 which should give error" -expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' http://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 -si -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"}}}' new "restconf Check interfaces eth/0/0 added" -expectfn "curl -s -X GET http://localhost/restconf/data/ietf-interfaces:interfaces" 0 '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up"}\]}}} +expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces" 0 '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up"}\]}}} ' new "restconf delete interfaces" -expectpart "$(curl -si -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -si -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 204 No Content" new "restconf Check empty config" -expectfn "curl -sG http://localhost/restconf/data/clixon-example:state" 0 "$state +expectfn "curl -sG $RCPROTO://localhost/restconf/data/clixon-example:state" 0 "$state " new "restconf Add interfaces subtree eth/0/0 using POST" -expectpart "$(curl -si -X POST http://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 -si -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" new "restconf Check eth/0/0 added config" -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://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 -si -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"}}\]}}' new "restconf Check eth/0/0 GET augmented state level 1" -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://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 -si -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"}}\]}' new "restconf Check eth/0/0 GET augmented state level 2" -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://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 -si -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"}}' new "restconf Check eth/0/0 added state XXXXXXX" -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/clixon-example:state)" 0 'HTTP/1.1 200 OK' '{"clixon-example:state":{"op":\["41","42","43"\]}}' +expectpart "$(curl -si -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"\]}}' new "restconf Re-post eth/0/0 which should generate error" -expectpart "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://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 -s -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"}}} ' new "Add leaf description using POST" -expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:description":"The-first-interface"}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -si -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" new "Add nothing using POST (expect fail)" -expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" http://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 -is -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"}}}' new "restconf Check description added" -expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"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"}}]}} +expecteq "$(curl -s -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"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" -expectpart "$(curl -si -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -si -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 204 No Content" new "Check deleted eth/0/0" -expectfn 'curl -s -G http://localhost/restconf/data' 0 "$state" +expectfn "curl -s -X GET http://localhost/restconf/data" 0 "$state" new "restconf Re-Delete eth/0/0 using none should generate error" -expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-missing","error-severity":"error","error-message":"Data does not exist; cannot delete resource"}}} ' +expecteq "$(curl -s -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 '{"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" -expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -si -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" new "restconf get subtree" -expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"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"}}]}} +expecteq "$(curl -s -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"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" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"42","y":"42"}} +expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"42","y":"42"}} ' if ! $YANG_UNKNOWN_ANYDATA ; then new "restconf rpc using POST json wrong" -expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-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 -si -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":"bad-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 new "restconf rpc non-existing rpc without namespace" -expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://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 -is -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"}}' new "restconf rpc non-existing rpc" -expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://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 -is -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"}}' new "restconf rpc missing name" -expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://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 -is -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"}}}' new "restconf rpc missing input" -expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://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 -is -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"}}}' new "restconf rpc using POST xml" -ret=$(curl -s -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":42}}' http://localhost/restconf/operations/clixon-example:example) +ret=$(curl -s -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) expect='4242' match=`echo $ret | grep --null -Eo "$expect"` if [ -z "$match" ]; then @@ -263,10 +263,10 @@ if [ -z "$match" ]; then fi new "restconf rpc using wrong prefix" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:example)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"yang module not found"}}} ' +expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' $RCPROTO://localhost/restconf/operations/wrong:example)" 0 '{"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" -ret=$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":"example"}}' http://localhost/restconf/operations/clixon-example:client-rpc) +ret=$(curl -s -i -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) expect='example' match=`echo $ret | grep --null -Eo "$expect"` if [ -z "$match" ]; then @@ -274,10 +274,10 @@ if [ -z "$match" ]; then fi new "restconf Add subtree without key (expected error)" -expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://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 -is -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' new "restconf Add subtree with too many keys (expected error)" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}} ' +expecteq "$(curl -s -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 '{"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 new "Kill restconf daemon" diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index 50a66ddb..6a602263 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -94,22 +94,22 @@ if [ $RC -ne 0 ]; then fi new "restconf POST tree without key" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"type":"regular"}}}' http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"name"},"error-severity":"error","error-message":"Mandatory key"}}} ' +expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"name"},"error-severity":"error","error-message":"Mandatory key"}}} ' new "restconf POST initial tree" -expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}' http://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" new "restconf POST top without namespace" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"cont1":{"interface":{"name":"local0","type":"regular"}}}' http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object cont1 is not qualified with namespace which is a MUST according to RFC 7951"}}} ' +expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"cont1":{"interface":{"name":"local0","type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object cont1 is not qualified with namespace which is a MUST according to RFC 7951"}}} ' new "restconf GET datastore initial" -expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}' +expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}' new "restconf GET interface subtree" -expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1/interface=local0" 0 '{"example:interface":\[{"name":"local0","type":"regular"}\]}' +expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0" 0 '{"example:interface":\[{"name":"local0","type":"regular"}\]}' new "restconf GET interface subtree xml" -ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/example:cont1/interface=local0) +ret=$(curl -s -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0) expect='local0regular' match=`echo $ret | grep --null -Eo "$expect"` if [ -z "$match" ]; then @@ -117,83 +117,83 @@ if [ -z "$match" ]; then fi new "restconf GET if-type" -expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1/interface=local0/type" 0 '{"example:type":"regular"}' +expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0/type" 0 '{"example:type":"regular"}' new "restconf POST interface without mandatory type" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:cont1 -d '{"example:interface":{"name":"TEST"}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"type"},"error-severity":"error","error-message":"Mandatory variable"}}} ' +expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:cont1 -d '{"example:interface":{"name":"TEST"}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"type"},"error-severity":"error","error-message":"Mandatory variable"}}} ' new "restconf POST interface without mandatory key" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:cont1 -d '{"example:interface":{"type":"regular"}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"name"},"error-severity":"error","error-message":"Mandatory key"}}} ' +expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:cont1 -d '{"example:interface":{"type":"regular"}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"name"},"error-severity":"error","error-message":"Mandatory key"}}} ' new "restconf POST interface" -expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 201 Created" new "restconf POST interface without namespace" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"interface":{"name":"TEST2","type":"eth0"}}' http://localhost/restconf/data/example:cont1)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object interface is not qualified with namespace which is a MUST according to RFC 7951"}}} ' +expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"interface":{"name":"TEST2","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object interface is not qualified with namespace which is a MUST according to RFC 7951"}}} ' new "restconf POST again" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/example:cont1)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}} ' +expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1)" 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 POST from top" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"TEST","type":"eth0"}}}' http://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"}}} ' +expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"TEST","type":"eth0"}}}' $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"}}} ' new "restconf DELETE" -expectfn 'curl -si -X DELETE http://localhost/restconf/data/example:cont1' 0 "HTTP/1.1 204 No Content" +expectfn "curl -si -X DELETE $RCPROTO://localhost/restconf/data/example:cont1" 0 "HTTP/1.1 204 No Content" new "restconf GET null datastore" -expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' +expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' new "restconf POST initial tree" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}' http://localhost/restconf/data)" 0 "" +expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 "" new "restconf GET initial tree" -expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}' +expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}' new "restconf DELETE whole datastore" -expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 "" +expectfn "curl -s -X DELETE $RCPROTO://localhost/restconf/data" 0 "" new "restconf GET null datastore" -expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' +expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' new "restconf PUT initial datastore" -expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' http://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" new "restconf GET datastore" -expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}' +expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}' new "restconf PUT replace datastore" -expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont2":{"name":"foo"}}}' http://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont2":{"name":"foo"}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" new "restconf GET replaced datastore" -expectfn "curl -s -X GET http://localhost/restconf/data/example:cont2" 0 '{"example:cont2":{"name":"foo"}}' +expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont2" 0 '{"example:cont2":{"name":"foo"}}' new "restconf PUT initial datastore again" -expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' http://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" new "restconf PUT change interface" -expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"local0","type":"atm0"}}' http://localhost/restconf/data/example:cont1/interface=local0)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"local0","type":"atm0"}}' $RCPROTO://localhost/restconf/data/example:cont1/interface=local0)" 0 "HTTP/1.1 204 No Content" new "restconf GET datastore atm" -expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"atm0"}\]}}' +expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"atm0"}\]}}' new "restconf PUT add interface" -expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/example:cont1/interface=TEST)" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1/interface=TEST)" 0 "HTTP/1.1 201 Created" new "restconf PUT change key error" -expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"ALPHA","type":"eth0"}}' http://localhost/restconf/data/example:cont1/interface=TEST)" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} ' +expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"ALPHA","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1/interface=TEST)" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} ' new "restconf PUT change type to eth0 (non-key sub-element to list)" -expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:type":"eth0"}' http://localhost/restconf/data/example:cont1/interface=local0/type)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:type":"eth0"}' $RCPROTO://localhost/restconf/data/example:cont1/interface=local0/type)" 0 "HTTP/1.1 204 No Content" new "restconf GET datastore eth" -expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1/interface=local0" 0 '{"example:interface":\[{"name":"local0","type":"eth0"}\]}' +expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0" 0 '{"example:interface":\[{"name":"local0","type":"eth0"}\]}' #--------------- json type tests new "restconf POST type x3" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}}' http://localhost/restconf/data)" 0 '' +expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}}' $RCPROTO://localhost/restconf/data)" 0 '' new "restconf POST type x3" -expectfn 'curl -s -X GET http://localhost/restconf/data/example:types' 0 '{"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}}' +expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:types" 0 '{"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}}' if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_restconf_err.sh b/test/test_restconf_err.sh index 59fb0ecd..dd585d7a 100755 --- a/test/test_restconf_err.sh +++ b/test/test_restconf_err.sh @@ -169,54 +169,54 @@ if [ $RC -ne 0 ]; then fi new "restconf POST initial tree" -expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' -d "$XML" http://localhost/restconf/data)" 0 'HTTP/1.1 201 Created' +expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' -d "$XML" $RCPROTO://localhost/restconf/data)" 0 'HTTP/1.1 201 Created' new "restconf GET initial datastore" -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/example:a=0)" 0 'HTTP/1.1 200 OK' "$XML" +expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example:a=0)" 0 'HTTP/1.1 200 OK' "$XML" new "restconf GET non-qualified list" -expectpart "$(curl -si -X GET http://localhost/restconf/data/example:a)" 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 =example:a, expected '=restval'\"}}}" +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example:a)" 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 =example:a, expected '=restval'\"}}}" new "restconf GET non-qualified list subelements" -expectpart "$(curl -si -X GET http://localhost/restconf/data/example:a/k)" 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 =example:a, expected '=restval'\"}}}" +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example:a/k)" 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 =example:a, expected '=restval'\"}}}" new "restconf GET non-existent container body" -expectpart "$(curl -si -X GET http://localhost/restconf/data/example:a=0/c)" 0 'HTTP/1.1 404 Not Found' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example:a=0/c)" 0 'HTTP/1.1 404 Not Found' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' new "restconf GET invalid (no yang) container body" -expectpart "$(curl -si -X GET http://localhost/restconf/data/example:a=0/xxx)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"xxx"},"error-severity":"error","error-message":"Unknown element"}}}' +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example:a=0/xxx)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"xxx"},"error-severity":"error","error-message":"Unknown element"}}}' new "restconf GET invalid (no yang) element" -expectpart "$(curl -si -X GET http://localhost/restconf/data/example:xxx)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"xxx"},"error-severity":"error","error-message":"Unknown element"}}}' +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example:xxx)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"xxx"},"error-severity":"error","error-message":"Unknown element"}}}' new "restconf POST non-existent (no yang) element" # should be invalid element -expectpart "$(curl -is -X POST -H 'Content-Type: application/yang-data+xml' -d "$XML" http://localhost/restconf/data/example:a=23/xxx)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"xxx"},"error-severity":"error","error-message":"Unknown element"}}}' +expectpart "$(curl -is -X POST -H 'Content-Type: application/yang-data+xml' -d "$XML" $RCPROTO://localhost/restconf/data/example:a=23/xxx)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"xxx"},"error-severity":"error","error-message":"Unknown element"}}}' # Test for multi-module path where an augment stretches across modules new "restconf POST augment multi-namespace path" -expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' -d '23' http://localhost/restconf/data)" 0 'HTTP/1.1 201 Created' +expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' -d '23' $RCPROTO://localhost/restconf/data)" 0 'HTTP/1.1 201 Created' new "restconf GET augment multi-namespace top" -expectpart "$(curl -si -X GET http://localhost/restconf/data/augment:route-config)" 0 'HTTP/1.1 200 OK' '{"augment:route-config":{"dynamic":{"example:ospf":{"reference-bandwidth":23}}}}' +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/augment:route-config)" 0 'HTTP/1.1 200 OK' '{"augment:route-config":{"dynamic":{"example:ospf":{"reference-bandwidth":23}}}}' new "restconf GET augment multi-namespace level 1" -expectpart "$(curl -si -X GET http://localhost/restconf/data/augment:route-config/dynamic)" 0 'HTTP/1.1 200 OK' '{"augment:dynamic":{"example:ospf":{"reference-bandwidth":23}}}' +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/augment:route-config/dynamic)" 0 'HTTP/1.1 200 OK' '{"augment:dynamic":{"example:ospf":{"reference-bandwidth":23}}}' new "restconf GET augment multi-namespace cross" -expectpart "$(curl -si -X GET http://localhost/restconf/data/augment:route-config/dynamic/example:ospf)" 0 'HTTP/1.1 200 OK' '{"example:ospf":{"reference-bandwidth":23}}' +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/augment:route-config/dynamic/example:ospf)" 0 'HTTP/1.1 200 OK' '{"example:ospf":{"reference-bandwidth":23}}' new "restconf GET augment multi-namespace cross level 2" -expectpart "$(curl -si -X GET http://localhost/restconf/data/augment:route-config/dynamic/example:ospf/reference-bandwidth)" 0 'HTTP/1.1 200 OK' '{"example:reference-bandwidth":23}' +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/augment:route-config/dynamic/example:ospf/reference-bandwidth)" 0 'HTTP/1.1 200 OK' '{"example:reference-bandwidth":23}' # XXX actually no such element new "restconf GET augment multi-namespace, no 2nd module in api-path, fail" -expectpart "$(curl -si -X GET http://localhost/restconf/data/augment:route-config/dynamic/ospf)" 0 'HTTP/1.1 404 Not Found' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/augment:route-config/dynamic/ospf)" 0 'HTTP/1.1 404 Not Found' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' #---------------------------------------------- # Also generate an invalid state XML. This should generate an "Internal" error and the name of the new "restconf GET failed state" -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data?content=nonconfig)" 0 '412 Precondition Failed' 'applicationoperation-failedmystateerrorNo such yang module. Internal error, state callback returned invalid XML: example_backend' +expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data?content=nonconfig)" 0 '412 Precondition Failed' 'applicationoperation-failedmystateerrorNo such yang module. Internal error, state callback returned invalid XML: example_backend' if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_restconf_jukebox.sh b/test/test_restconf_jukebox.sh index ce9560d4..0cd91137 100755 --- a/test/test_restconf_jukebox.sh +++ b/test/test_restconf_jukebox.sh @@ -89,160 +89,160 @@ if [ $RC -ne 0 ]; then fi new "B.1.1. Retrieve the Top-Level API Resource root" -expectpart "$(curl -si -X GET -H 'Accept: application/xrd+xml' http://localhost/.well-known/host-meta)" 0 "HTTP/1.1 200 OK" "Content-Type: application/xrd+xml" "" "" "" +expectpart "$(curl -si -X GET -H 'Accept: application/xrd+xml' $RCPROTO://localhost/.well-known/host-meta)" 0 "HTTP/1.1 200 OK" "Content-Type: application/xrd+xml" "" "" "" d='{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2016-06-21"}}' new "B.1.1. Retrieve the Top-Level API Resource /restconf json" -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" "$d" +expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" "$d" new "B.1.1. Retrieve the Top-Level API Resource /restconf xml (not in RFC)" -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+xml" '2016-06-21' +expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+xml" '2016-06-21' # This just catches the header and the jukebox module, the RFC has foo and bar which # seems wrong to recreate new "B.1.2. Retrieve the Server Module Information" -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/ietf-yang-library:modules-state)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" '{"ietf-yang-library:modules-state":{"module-set-id":"0","module":\[{"name":"clixon-lib","revision":"2020-04-23","namespace":"http://clicon.org/lib","conformance-type":"implement"},{"name":"clixon-rfc5277","revision":"2008-07-01","namespace":"urn:ietf:params:xml:ns:netmod:notification","conformance-type":"implement"},{"name":"example-events","revision":"","namespace":"urn:example:events","conformance-type":"implement"},{"name":"example-jukebox","revision":"2016-08-15","namespace":"http://example.com/ns/example-jukebox","conformance-type":"implement"},{"name":"example-system","revision":"","namespace":"http://example.com/ns/example-system","conformance-type":"implement"},{"name":"ietf-inet-types","revision":"2013-07-15","namespace":"urn:ietf:params:xml:ns:yang:ietf-inet-types","conformance-type":"implement"},' +expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-yang-library:modules-state)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" '{"ietf-yang-library:modules-state":{"module-set-id":"0","module":\[{"name":"clixon-lib","revision":"2020-04-23","namespace":"http://clicon.org/lib","conformance-type":"implement"},{"name":"clixon-rfc5277","revision":"2008-07-01","namespace":"urn:ietf:params:xml:ns:netmod:notification","conformance-type":"implement"},{"name":"example-events","revision":"","namespace":"urn:example:events","conformance-type":"implement"},{"name":"example-jukebox","revision":"2016-08-15","namespace":"http://example.com/ns/example-jukebox","conformance-type":"implement"},{"name":"example-system","revision":"","namespace":"http://example.com/ns/example-system","conformance-type":"implement"},{"name":"ietf-inet-types","revision":"2013-07-15","namespace":"urn:ietf:params:xml:ns:yang:ietf-inet-types","conformance-type":"implement"},' new "B.1.3. Retrieve the Server Capability Information" -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/capabilities)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+xml" 'Cache-Control: no-cache' 'urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=expliciturn:ietf:params:restconf:capability:depth +expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/capabilities)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+xml" 'Cache-Control: no-cache' 'urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=expliciturn:ietf:params:restconf:capability:depth ' new "B.2.1. Create New Data Resources (artist+json)" -expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library -d '{"example-jukebox:artist":[{"name":"Foo Fighters"}]}')" 0 "HTTP/1.1 201 Created" "Location: http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters" +expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library -d '{"example-jukebox:artist":[{"name":"Foo Fighters"}]}')" 0 "HTTP/1.1 201 Created" "Location: http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters" new "B.2.1. Create New Data Resources (album+xml)" -expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d 'Wasting Light2011')" 0 "HTTP/1.1 201 Created" "Location: http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light" +expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d 'Wasting Light2011')" 0 "HTTP/1.1 201 Created" "Location: http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light" new "B.2.1. Add Data Resources again (conflict - not in RFC)" -expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d 'Wasting Light2011')" 0 "HTTP/1.1 409 Conflict" +expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d 'Wasting Light2011')" 0 "HTTP/1.1 409 Conflict" new "4.5. PUT replace content (xml encoding)" -expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d 'Wasting Lightjbox:alternative2011')" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d 'Wasting Lightjbox:alternative2011')" 0 "HTTP/1.1 204 No Content" new "4.5. PUT create new identity" -expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":[{"name":"London Calling","year":1979}]}')" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":[{"name":"London Calling","year":1979}]}')" 0 "HTTP/1.1 201 Created" new "4.5. Check jukebox content: 1 Clash and 1 Foo fighters album" -expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'ClashLondon Calling1979Foo FightersWasting Lightjbox:alternative2011' +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'ClashLondon Calling1979Foo FightersWasting Lightjbox:alternative2011' new "B.2.2. Added genre (preamble to actual test)" -expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '{"example-jukebox:album":[{"name":"Wasting Light","genre":"example-jukebox:alternative","year":2011}]}')" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '{"example-jukebox:album":[{"name":"Wasting Light","genre":"example-jukebox:alternative","year":2011}]}')" 0 "HTTP/1.1 204 No Content" # First use of PATCH new "B.2.2. Detect Datastore Resource Entity-Tag Change (XXX if-unmodified)" -expectpart "$(curl -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light/genre -d '{"example-jukebox:genre":"example-jukebox:alternative"}')" 0 'HTTP/1.1 204 No Content' +expectpart "$(curl -si -X PATCH -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light/genre -d '{"example-jukebox:genre":"example-jukebox:alternative"}')" 0 'HTTP/1.1 204 No Content' new "B.2.3. Edit a Datastore Resource (Add 1 Foo fighter and Nick cave album)" -expectpart "$(curl -si -X PATCH -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data -d 'trueFoo FightersOne by One2012Nick Cave and the Bad SeedsTender Prey1988')" 0 'HTTP/1.1 204 No Content' +expectpart "$(curl -si -X PATCH -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data -d 'trueFoo FightersOne by One2012Nick Cave and the Bad SeedsTender Prey1988')" 0 'HTTP/1.1 204 No Content' new "B.2.3. Check patch system" -expectpart "$(curl -si -X GET http://localhost/restconf/data/example-system:system -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'true' +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-system:system -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'true' new "B.2.3. Check jukebox: 1 Clash, 2 Foo Fighters, 1 Nick Cave" -expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'ClashLondon Calling1979Foo FightersOne by One2012Wasting Lightalternative2011Nick Cave and the Bad SeedsTender Prey1988' +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'ClashLondon Calling1979Foo FightersOne by One2012Wasting Lightalternative2011Nick Cave and the Bad SeedsTender Prey1988' new "B.2.4. Replace a Datastore Resource" -expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data -d 'Foo FightersOne by One2012Nick Cave and the Bad SeedsTender Prey1988')" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data -d 'Foo FightersOne by One2012Nick Cave and the Bad SeedsTender Prey1988')" 0 "HTTP/1.1 204 No Content" new "B.2.4. Check replace" -expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'Foo FightersOne by One2012Nick Cave and the Bad SeedsTender Prey1988' +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'Foo FightersOne by One2012Nick Cave and the Bad SeedsTender Prey1988' new "B.2.5. Edit a Data Resource (add Nick cave album The good son)" -expectpart "$(curl -si -X PATCH http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Nick%20Cave%20and%20the%20Bad%20Seeds -H 'Content-Type: application/yang-data+xml' -d 'Nick Cave and the Bad SeedsThe Good Son1990')" 0 'HTTP/1.1 204 No Content' +expectpart "$(curl -si -X PATCH $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Nick%20Cave%20and%20the%20Bad%20Seeds -H 'Content-Type: application/yang-data+xml' -d 'Nick Cave and the Bad SeedsThe Good Son1990')" 0 'HTTP/1.1 204 No Content' new "B.2.5. Check edit" -expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Nick%20Cave%20and%20the%20Bad%20Seeds -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'Nick Cave and the Bad SeedsTender Prey1988The Good Son1990' +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Nick%20Cave%20and%20the%20Bad%20Seeds -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'Nick Cave and the Bad SeedsTender Prey1988The Good Son1990' # note reverse order of down/up as it is ordered by system and down is before up new 'B.3.1. "content" Parameter (preamble, add content)' -expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-events:events -d '{"example-events:events":{"event":[{"name":"interface-down","description":"Interface down notification count"},{"name":"interface-up","description":"Interface up notification count"}]}}')" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-events:events -d '{"example-events:events":{"event":[{"name":"interface-down","description":"Interface down notification count"},{"name":"interface-up","description":"Interface up notification count"}]}}')" 0 "HTTP/1.1 201 Created" new 'B.3.1. "content" Parameter (wrong content)' -expectpart "$(curl -si -X GET http://localhost/restconf/data/example-events:events?content=kalle -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-attribute","error-info":{"bad-attribute":"content"},"error-severity":"error","error-message":"Unrecognized value of content attribute"}}}' +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-events:events?content=kalle -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-attribute","error-info":{"bad-attribute":"content"},"error-severity":"error","error-message":"Unrecognized value of content attribute"}}}' new 'B.3.1. "content" Parameter example 1: content=all' -expectpart "$(curl -si -X GET http://localhost/restconf/data/example-events:events?content=all -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-events:events":{"event":\[{"name":"interface-down","description":"Interface down notification count","event-count":90},{"name":"interface-up","description":"Interface up notification count","event-count":77}\]}}' +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-events:events?content=all -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-events:events":{"event":\[{"name":"interface-down","description":"Interface down notification count","event-count":90},{"name":"interface-up","description":"Interface up notification count","event-count":77}\]}}' new 'B.3.1. "content" Parameter example 2: content=config' -expectpart "$(curl -si -X GET http://localhost/restconf/data/example-events:events?content=config -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-events:events":{"event":\[{"name":"interface-down","description":"Interface down notification count"},{"name":"interface-up","description":"Interface up notification count"}\]}}' +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-events:events?content=config -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-events:events":{"event":\[{"name":"interface-down","description":"Interface down notification count"},{"name":"interface-up","description":"Interface up notification count"}\]}}' new 'B.3.1. "content" Parameter example 3: content=nonconfig' -expectpart "$(curl -si -X GET http://localhost/restconf/data/example-events:events?content=nonconfig -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-events:events":{"event":\[{"name":"interface-down","event-count":90},{"name":"interface-up","event-count":77}\]}}' +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-events:events?content=nonconfig -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-events:events":{"event":\[{"name":"interface-down","event-count":90},{"name":"interface-up","event-count":77}\]}}' #new "restconf DELETE whole datastore" -#expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 "" +#expectfn "curl -s -X DELETE $RCPROTO://localhost/restconf/data" 0 "" new 'B.3.2. "depth" Parameter example 1 unbound' -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox?depth=unbounded)" 0 "HTTP/1.1 200 OK" '{"example-jukebox:jukebox":{"library":{"artist":\[{"name":"Foo Fighters","album":\[{"name":"One by One","year":2012}\]},{"name":"Nick Cave and the Bad Seeds","album":\[{"name":"Tender Prey","year":1988},{"name":"The Good Son","year":1990}\]}\]}}}' +expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox?depth=unbounded)" 0 "HTTP/1.1 200 OK" '{"example-jukebox:jukebox":{"library":{"artist":\[{"name":"Foo Fighters","album":\[{"name":"One by One","year":2012}\]},{"name":"Nick Cave and the Bad Seeds","album":\[{"name":"Tender Prey","year":1988},{"name":"The Good Son","year":1990}\]}\]}}}' new 'B.3.2. "depth" Parameter example 2 depth=1' -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox?depth=1)" 0 "HTTP/1.1 200 OK" '{"example-jukebox:jukebox":{}}' +expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox?depth=1)" 0 "HTTP/1.1 200 OK" '{"example-jukebox:jukebox":{}}' new 'B.3.2. "depth" Parameter depth=2' -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox?depth=2)" 0 "HTTP/1.1 200 OK" '{"example-jukebox:jukebox":{"library":{}}}' +expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox?depth=2)" 0 "HTTP/1.1 200 OK" '{"example-jukebox:jukebox":{"library":{}}}' # Maybe this is not correct w [null,null]but I have no good examples new 'B.3.2. "depth" Parameter depth=3' -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox?depth=3)" 0 "HTTP/1.1 200 OK" '{"example-jukebox:jukebox":{"artist":\[null,null\]}}} +expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox?depth=3)" 0 "HTTP/1.1 200 OK" '{"example-jukebox:jukebox":{"artist":\[null,null\]}}} ' new "restconf DELETE whole datastore" -expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 "" +expectfn "curl -s -X DELETE $RCPROTO://localhost/restconf/data" 0 "" #new 'B.3.3. "fields" Parameter' new 'B.3.4. "insert" Parameter' JSON="{\"example-jukebox:song\":[{\"index\":1,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Rope']\"}]}" -expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=1' +expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=1' new 'B.3.4. "insert" Parameter first (RFC example says after)' JSON="{\"example-jukebox:song\":[{\"index\":0,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Bridge Burning']\"}]}" -expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=0' +expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=0' new 'B.3.4. "insert" Parameter check order' -RES="Foo-One0/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]1/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]" +RES="Foo-One0/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]1/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]" expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES" new 'B.3.5. "point" Parameter (before for more interesting order: 0,2,1)' JSON="{\"example-jukebox:song\":[{\"index\":2,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Bridge Burning']\"}]}" -expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' -d "$JSON" http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=before\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D1 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=2' +expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' -d "$JSON" $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=before\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D1 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=2' new 'B.3.5. "point" check order (0,2,1)' RES="Foo-One0/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]2/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]1/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]" -expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES" +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES" new 'B.3.5. "point" Parameter 3 after 2 (using PUT)' JSON="{\"example-jukebox:song\":[{\"index\":3,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Something else']\"}]}" -expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' -d "$JSON" http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=3?insert=after\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D2 )" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' -d "$JSON" $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=3?insert=after\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D2 )" 0 "HTTP/1.1 201 Created" new 'B.3.5. "point" check order (0,2,3,1)' RES="Foo-One0/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]2/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]3/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Something else'\]1/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]" -expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES" +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES" new "restconf DELETE whole datastore" -expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 "" +expectfn "curl -s -X DELETE $RCPROTO://localhost/restconf/data" 0 "" new 'B.3.4. "insert/point" leaf-list 3 (not in RFC)' -expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"3"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=3' +expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"3"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=3' new 'B.3.4. "insert/point" leaf-list 2 first' -expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=first -d '{"example-jukebox:extra":"2"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=2' +expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data?insert=first -d '{"example-jukebox:extra":"2"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=2' new 'B.3.4. "insert/point" leaf-list 1 last' -expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=1' +expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=1' #new 'B.3.4. "insert/point" move leaf-list 1 last' #- restconf cannot move a leaf-list(list?) item -#expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=1' +#expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=1' new 'B.3.5. "insert/point" leaf-list check order (2,3,1)' -expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '231' +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '231' new 'B.3.5. "point" Parameter leaf-list 4 before 3' -expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' -d '{"example-jukebox:extra":"4"}' http://localhost/restconf/data?insert=before\&point=%2Fexample-jukebox%3Aextra%3D3 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=4' +expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' -d '{"example-jukebox:extra":"4"}' $RCPROTO://localhost/restconf/data?insert=before\&point=%2Fexample-jukebox%3Aextra%3D3 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=4' new 'B.3.5. "insert/point" leaf-list check order (2,4,3,1)' -expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '2431' +expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '2431' new "B.2.2. Detect Datastore Resource Entity-Tag Change" # XXX done except entity-changed new 'B.3.3. "fields" Parameter' diff --git a/test/test_restconf_listkey.sh b/test/test_restconf_listkey.sh index 26fb7f1c..1150d599 100755 --- a/test/test_restconf_listkey.sh +++ b/test/test_restconf_listkey.sh @@ -92,78 +92,78 @@ if [ $RC -ne 0 ]; then fi new "restconf PUT add whole list entry" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"x","c":"y","nonkey":"0"}}')" 0 '' +expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"x","c":"y","nonkey":"0"}}')" 0 '' # GETs to ensure you get list [] in JSON new "restconf GET whole list entry" -expecteq "$(curl -s -X GET -H "Accept: application/yang-data+json" http://localhost/restconf/data/list:c/)" 0 '{"list:c":{"a":[{"b":"x","c":"y","nonkey":"0"}]}} +expecteq "$(curl -s -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/)" 0 '{"list:c":{"a":[{"b":"x","c":"y","nonkey":"0"}]}} ' new "restconf GET list entry itself (should fail)" -expectpart "$(curl -si -X GET -H "Accept: application/yang-data+json" http://localhost/restconf/data/list:c/a)" 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 =a, expected ' +expectpart "$(curl -si -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a)" 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 =a, expected ' new "restconf GET list entry" -expecteq "$(curl -s -X GET -H "Accept: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y)" 0 '{"list:a":[{"b":"x","c":"y","nonkey":"0"}]} +expecteq "$(curl -s -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y)" 0 '{"list:a":[{"b":"x","c":"y","nonkey":"0"}]} ' new "restconf PUT add whole list entry XML" -expecteq "$(curl -s -X PUT -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d 'xxxy0' http://localhost/restconf/data/list:c/a=xx,xy)" 0 '' +expecteq "$(curl -s -X PUT -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d 'xxxy0' $RCPROTO://localhost/restconf/data/list:c/a=xx,xy)" 0 '' -expectpart "$(curl -si -X GET -H "Accept: application/yang-data+json" http://localhost/restconf/data/list:c/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":"malformed key =a, expected ' +expectpart "$(curl -si -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/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":"malformed key =a, expected ' new "restconf PUT change whole list entry (same keys)" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"x","c":"y","nonkey":"z"}}')" 0 '' +expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"x","c":"y","nonkey":"z"}}')" 0 '' new "restconf PUT change whole list entry (no namespace)(expect fail)" -expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y -d '{"a":{"b":"x","c":"y","nonkey":"z"}}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object a is not qualified with namespace which is a MUST according to RFC 7951"}}} ' +expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"a":{"b":"x","c":"y","nonkey":"z"}}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object a is not qualified with namespace which is a MUST according to RFC 7951"}}} ' new "restconf PUT change list entry (wrong keys)(expect fail)" -expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"y","c":"x"}}')" 0 '412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} ' +expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"y","c":"x"}}')" 0 '412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} ' new "restconf PUT change list entry (wrong keys)(expect fail) XML" -expecteq "$(curl -s -X PUT -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d 'xyxz0' http://localhost/restconf/data/list:c/a=xx,xy)" 0 'protocoloperation-failederrorapi-path keys do not match data keys ' +expecteq "$(curl -s -X PUT -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d 'xyxz0' $RCPROTO://localhost/restconf/data/list:c/a=xx,xy)" 0 'protocoloperation-failederrorapi-path keys do not match data keys ' new "restconf PUT change list entry (just one key)(expect fail)" -expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x -d '{"list:a":{"b":"x"}}')" 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 a length mismatch"}}} ' +expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x -d '{"list:a":{"b":"x"}}')" 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 a length mismatch"}}} ' new "restconf PUT sub non-key" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/nonkey -d '{"list:nonkey":"u"}')" 0 '' +expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/nonkey -d '{"list:nonkey":"u"}')" 0 '' new "restconf PUT sub key same value" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/b -d '{"list:b":"x"}')" 0 '' +expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/b -d '{"list:b":"x"}')" 0 '' new "restconf PUT just key other value (should fail)" -expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x/b -d '{"b":"y"}')" 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 a length mismatch"}}}' +expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x/b -d '{"b":"y"}')" 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 a length mismatch"}}}' new "restconf PUT add leaf-list entry" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/d=x -d '{"list:d":"x"}')" 0 '' +expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/d=x -d '{"list:d":"x"}')" 0 '' new "restconf PUT change leaf-list entry (expect fail)" -expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/d=x -d '{"list:d":"y"}')" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}' +expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/d=x -d '{"list:d":"y"}')" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}' new "restconf PUT list-list" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/e=z -d '{"list:e":{"f":"z","nonkey":"0"}}')" 0 '' +expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z -d '{"list:e":{"f":"z","nonkey":"0"}}')" 0 '' new "restconf PUT change list-lst entry (wrong keys)(expect fail)" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/e=z -d '{"list:e":{"f":"wrong","nonkey":"0"}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} ' +expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z -d '{"list:e":{"f":"wrong","nonkey":"0"}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} ' new "restconf PUT list-list sub non-key" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/e=z/nonkey -d '{"list:nonkey":"u"}')" 0 '' +expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z/nonkey -d '{"list:nonkey":"u"}')" 0 '' new "restconf PUT list-list single first key" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x/e=z/f -d '{"f":"z"}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}} ' +expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x/e=z/f -d '{"f":"z"}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}} ' new "restconf PUT list-list just key ok" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/e=z/f -d '{"list:f":"z"}')" 0 '' +expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z/f -d '{"list:f":"z"}')" 0 '' new "restconf PUT list-list just key just key wrong value (should fail)" -expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/e=z/f -d '{"list:f":"wrong"}')" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} ' +expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z/f -d '{"list:f":"wrong"}')" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} ' new "restconf PUT add list+leaf-list entry" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/f=u -d '{"list:f":"u"}')" 0 '' +expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/f=u -d '{"list:f":"u"}')" 0 '' new "restconf PUT change list+leaf-list entry (expect fail)" -expectpart "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/f=u -d '{"list:f":"w"}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}' +expectpart "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/f=u -d '{"list:f":"w"}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}' if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_restconf_patch.sh b/test/test_restconf_patch.sh index 6facdc0c..2bf14e38 100755 --- a/test/test_restconf_patch.sh +++ b/test/test_restconf_patch.sh @@ -119,80 +119,80 @@ wait_restconf # also in test_restconf.sh new "MUST support the PATCH method for a plain patch" -expectpart "$(curl -u andy:bar -is -X OPTIONS http://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE" "Accept-Patch: application/yang-data+xml,application/yang-data+json" +expectpart "$(curl -u andy:bar -is -X OPTIONS $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE" "Accept-Patch: application/yang-data+xml,application/yang-data+json" new "If the target resource instance does not exist, the server MUST NOT create it." -expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox -d '{"example-jukebox:jukebox":null}')" 0 "HTTP/1.1 400 Bad Request" +expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -d '{"example-jukebox:jukebox":null}')" 0 "HTTP/1.1 400 Bad Request" new "Create it with PUT instead" -expectpart "$(curl -u andy:bar -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox -d '{"example-jukebox:jukebox":null}')" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -u andy:bar -si -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -d '{"example-jukebox:jukebox":null}')" 0 "HTTP/1.1 201 Created" new "THEN change it with PATCH" -expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox -d '{"example-jukebox:jukebox":{"library":{"artist":{"name":"Clash"}}}}')" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -d '{"example-jukebox:jukebox":{"library":{"artist":{"name":"Clash"}}}}')" 0 "HTTP/1.1 204 No Content" new "Check content (json)" -expectpart "$(curl -u andy:bar -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-jukebox:jukebox":{"library":{"artist":\[{"name":"Clash"}\]}}}' +expectpart "$(curl -u andy:bar -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-jukebox:jukebox":{"library":{"artist":\[{"name":"Clash"}\]}}}' new "Check content (xml)" -expectpart "$(curl -u andy:bar -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'Clash' +expectpart "$(curl -u andy:bar -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'Clash' new 'If the user is not authorized, "403 Forbidden" SHOULD be returned.' -expectpart "$(curl -u wilma:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash -d '{"example-jukebox:artist":{"name":"Clash","album":{"name":"London Calling"}}}')" 0 "HTTP/1.1 403 Forbidden" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' +expectpart "$(curl -u wilma:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash -d '{"example-jukebox:artist":{"name":"Clash","album":{"name":"London Calling"}}}')" 0 "HTTP/1.1 403 Forbidden" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' new 'user is authorized' -expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash -d '{"example-jukebox:artist":{"name":"Clash","album":{"name":"London Calling"}}}')" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash -d '{"example-jukebox:artist":{"name":"Clash","album":{"name":"London Calling"}}}')" 0 "HTTP/1.1 204 No Content" # 4.6.1. Plain Patch new "restconf DELETE whole datastore" -expectpart "$(curl -u andy:bar -is -X DELETE http://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -u andy:bar -is -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" new "Create album London Calling with PUT" -expectpart "$(curl -u andy:bar -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":{"name":"London Calling"}}')" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -u andy:bar -si -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":{"name":"London Calling"}}')" 0 "HTTP/1.1 201 Created" new "The message-body for a plain patch MUST be present" -expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Beatles -d '')" 0 "HTTP/1.1 400 Bad Request" +expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Beatles -d '')" 0 "HTTP/1.1 400 Bad Request" # Plain patch can be used to create or update, but not delete, a child # resource within the target resource. new "Create a child resource (genre and year)" -expectpart "$(curl -u andy:bar -si -X PATCH -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":{"name":"London Calling","genre":"example-jukebox:rock","year":"2129"}}')" 0 'HTTP/1.1 204 No Content' +expectpart "$(curl -u andy:bar -si -X PATCH -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":{"name":"London Calling","genre":"example-jukebox:rock","year":"2129"}}')" 0 'HTTP/1.1 204 No Content' new "Update a child resource (year)" -expectpart "$(curl -u andy:bar -si -X PATCH -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":{"name":"London Calling","year":"1979"}}')" 0 'HTTP/1.1 204 No Content' +expectpart "$(curl -u andy:bar -si -X PATCH -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":{"name":"London Calling","year":"1979"}}')" 0 'HTTP/1.1 204 No Content' new "Check content xml" -expectpart "$(curl -u andy:bar -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'London Callingrock1979' +expectpart "$(curl -u andy:bar -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'London Callingrock1979' new "Check content json" -expectpart "$(curl -u andy:bar -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-jukebox:album":\[{"name":"London Calling","genre":"rock","year":1979}\]}' +expectpart "$(curl -u andy:bar -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-jukebox:album":\[{"name":"London Calling","genre":"rock","year":1979}\]}' new "The message-body MUST be represented by the media type application/yang-data+xml (or +json ^)" -expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d 'London Callingjazz')" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d 'London Callingjazz')" 0 "HTTP/1.1 204 No Content" new "Check content (xml)" -expectpart "$(curl -u andy:bar -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'ClashLondon Callingjazz1979' +expectpart "$(curl -u andy:bar -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'ClashLondon Callingjazz1979' new "not implemented media type" -expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-patch+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d 'London Callingjazz')" 0 "HTTP/1.1 501 Not Implemented" +expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-patch+xml' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d 'London Callingjazz')" 0 "HTTP/1.1 501 Not Implemented" new "wrong media type" -expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: text/html' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d 'London Callingjazz')" 0 "HTTP/1.1 415 Unsupported Media Type" +expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: text/html' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d 'London Callingjazz')" 0 "HTTP/1.1 415 Unsupported Media Type" # If the target resource represents a YANG leaf-list, then the PATCH # method MUST NOT change the value of the leaf-list instance. # leaf-list extra{ new "Create leaf-list a" -expectpart "$(curl -u andy:bar -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:extra=a -d '{"example-jukebox:extra":"a"}')" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -u andy:bar -si -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:extra=a -d '{"example-jukebox:extra":"a"}')" 0 "HTTP/1.1 201 Created" new "Create leaf-list b" -expectpart "$(curl -u andy:bar -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:extra=b -d '{"example-jukebox:extra":"b"}')" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -u andy:bar -si -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:extra=b -d '{"example-jukebox:extra":"b"}')" 0 "HTTP/1.1 201 Created" new "Check content" -expectpart "$(curl -u andy:bar -si -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-jukebox:extra":\["a","b"\]}' +expectpart "$(curl -u andy:bar -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-jukebox:extra":\["a","b"\]}' new "MUST NOT change the value of the leaf-list instance" -expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:extra=a -d '{"example-jukebox:extra":"b"}')" 0 'HTTP/1.1 412 Precondition Failed' +expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:extra=a -d '{"example-jukebox:extra":"b"}')" 0 'HTTP/1.1 412 Precondition Failed' # If the target resource represents a YANG list instance, then the key # leaf values, in message-body representation, MUST be the same as the @@ -200,7 +200,7 @@ expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-d # used to change the key leaf values for a data resource instance. new "The key leaf values MUST be the same as the key leaf values in the request" -expectpart "$(curl -u andy:bar -si -X PATCH -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":{"name":"The Clash"}}')" 0 'HTTP/1.1 412 Precondition Failed' +expectpart "$(curl -u andy:bar -si -X PATCH -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":{"name":"The Clash"}}')" 0 'HTTP/1.1 412 Precondition Failed' new "Kill restconf daemon" stop_restconf diff --git a/test/test_restconf_startup.sh b/test/test_restconf_startup.sh index 5aaeb4da..87480978 100755 --- a/test/test_restconf_startup.sh +++ b/test/test_restconf_startup.sh @@ -74,16 +74,16 @@ testrun(){ wait_restconf new "restconf put 42" - expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:x/y=42 -d '{"example:y":{"a":"42","b":"42"}}')" 0 "" + expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:x/y=42 -d '{"example:y":{"a":"42","b":"42"}}')" 0 "" new "restconf put 99" - expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:x/y=99 -d '{"example:y":{"a":"99","b":"99"}}')" 0 "" + expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:x/y=99 -d '{"example:y":{"a":"99","b":"99"}}')" 0 "" new "restconf post 123" - expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:x -d '{"example:y":{"a":"123","b":"123"}}')" 0 "" + expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:x -d '{"example:y":{"a":"123","b":"123"}}')" 0 "" new "restconf delete 42" - expecteq "$(curl -s -X DELETE http://localhost/restconf/data/example:x/y=42)" 0 "" + expecteq "$(curl -s -X DELETE $RCPROTO://localhost/restconf/data/example:x/y=42)" 0 "" new "Kill restconf daemon" stop_restconf