diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ca1a0e5..59c00e4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,8 +34,12 @@ Expected: June 2021 ### New features -* Started EXPERIMENTAL HTTP/2 work using nghttp2 - * Added autoconf config options, temporary for nghttp2 development: `--disable-evhtp`and `--enable-nghttp2` enabling http/1 only / http/2 only linki and compile. +* HTTP/2 support using nghttp2 + * --with-restconf=fcgi not affected, only for --with-restconf=native + * Added autoconf config for --with-restconf=native: + * `--disable-evhtp` disabling http/1 + * `--enable-nghttp2` enabling http/2 + * Remaining work: http (non ALPN) h1->h2 upgrade * YANG when statement in conjunction with grouping/uses/augment * Several cases were not implemented fully according to RFC 7950: * Do not extend default values if when statements evaluate to false @@ -80,6 +84,7 @@ Developers may need to change their code ### Minor features +* Restconf: ensure HEAD method works everywhere GET does. * Added new startup-mode: `running-startup`: First try running db, if it is empty try startup db. * See [Can startup mode to be extended to support running-startup mode? #234](https://github.com/clicon/clixon/issues/234) * Restconf: added inline configuration using `-R ` command line as an alternative to making advanced restconf configuration diff --git a/apps/restconf/Makefile.in b/apps/restconf/Makefile.in index 5dbca293..290cc9db 100644 --- a/apps/restconf/Makefile.in +++ b/apps/restconf/Makefile.in @@ -100,6 +100,7 @@ APPSRC += restconf_methods_get.c APPSRC += restconf_root.c APPSRC += restconf_main_$(with_restconf).c ifeq ($(with_restconf),native) +APPSRC += restconf_native.c APPSRC += restconf_evhtp.c # HTTP/1 APPSRC += restconf_nghttp2.c # HTTP/2 endif diff --git a/apps/restconf/restconf_api.h b/apps/restconf/restconf_api.h index 4fe7c2c3..a44cb440 100644 --- a/apps/restconf/restconf_api.h +++ b/apps/restconf/restconf_api.h @@ -44,10 +44,11 @@ #if defined(__GNUC__) && __GNUC__ >= 3 int restconf_reply_header(void *req, const char *name, const char *vfmt, ...) __attribute__ ((format (printf, 3, 4))); #else -int restconf_reply_header(FCGX_Request *req, const char *name, const char *vfmt, ...); +int restconf_reply_header(void *req, const char *name, const char *vfmt, ...); #endif -int restconf_reply_send(void *req, int code, cbuf *cb); +/* note cb is consumed dont free */ +int restconf_reply_send(void *req, int code, cbuf *cb, int head); cbuf *restconf_get_indata(void *req); diff --git a/apps/restconf/restconf_api_fcgi.c b/apps/restconf/restconf_api_fcgi.c index c101e1d0..2eb788f3 100644 --- a/apps/restconf/restconf_api_fcgi.c +++ b/apps/restconf/restconf_api_fcgi.c @@ -186,14 +186,15 @@ restconf_reply_body_add(void *req0, /*! Send HTTP reply with potential message body * @param[in] req Fastcgi request handle * @param[in] code Status code - * @param[in] cb Body as a cbuf if non-NULL + * @param[in] cb Body as a cbuf if non-NULL. Note is consumed * * Prerequisites: status code set, headers given, body if wanted set */ int restconf_reply_send(void *req0, int code, - cbuf *cb) + cbuf *cb, + int head) { FCGX_Request *req = (FCGX_Request *)req0; int retval = -1; @@ -206,9 +207,12 @@ restconf_reply_send(void *req0, goto done; FCGX_FPrintF(req->out, "\r\n"); /* Write a body if cbuf is nonzero */ - if (cb != NULL && cbuf_len(cb)){ - FCGX_FPrintF(req->out, "%s", cbuf_get(cb)); - FCGX_FPrintF(req->out, "\r\n"); + if (cb != NULL){ + if (!head && cbuf_len(cb)){ + FCGX_FPrintF(req->out, "%s", cbuf_get(cb)); + FCGX_FPrintF(req->out, "\r\n"); + } + cbuf_free(cb); } FCGX_FFlush(req->out); /* Is this only for notification ? */ retval = 0; diff --git a/apps/restconf/restconf_api_native.c b/apps/restconf/restconf_api_native.c index 1bab4c4a..3917296b 100644 --- a/apps/restconf/restconf_api_native.c +++ b/apps/restconf/restconf_api_native.c @@ -60,7 +60,6 @@ #include #endif - /* cligen */ #include @@ -83,19 +82,23 @@ restconf_reply_header(void *req0, const char *vfmt, ...) { -#ifdef HAVE_LIBEVHTP - evhtp_request_t *req = (evhtp_request_t *)req0; - int retval = -1; - size_t vlen; - char *value = NULL; - va_list ap; - evhtp_connection_t *conn; - restconf_conn_h *rc; + int retval = -1; + restconf_stream_data *sd = (restconf_stream_data *)req0; + restconf_conn *rc; + 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; + clicon_debug(1, "%s %s", __FUNCTION__, name); + if (sd == NULL || name == NULL || vfmt == NULL){ + clicon_err(OE_CFG, EINVAL, "sd, name or value is NULL"); + goto done; } + if ((rc = sd->sd_conn) == NULL){ + clicon_err(OE_CFG, EINVAL, "rc is NULL"); + goto done; + } + /* First round: compute vlen and allocate value */ va_start(ap, vfmt); vlen = vsnprintf(NULL, 0, vfmt, ap); va_end(ap); @@ -104,7 +107,7 @@ restconf_reply_header(void *req0, clicon_err(OE_UNIX, errno, "malloc"); goto done; } - /* second round: compute actual value */ + /* Second round: compute actual value */ va_start(ap, vfmt); if (vsnprintf(value, vlen+1, vfmt, ap) < 0){ clicon_err(OE_UNIX, errno, "vsnprintf"); @@ -112,15 +115,7 @@ restconf_reply_header(void *req0, goto done; } va_end(ap); - if ((conn = evhtp_request_get_connection(req)) == NULL){ - clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection"); - goto done; - } - if ((rc = conn->arg) == NULL){ - clicon_err(OE_RESTCONF, EFAULT, "Internal error: restconf-conn-h is NULL: shouldnt happen"); - goto done; - } - if (cvec_add_string(rc->rc_outp_hdrs, (char*)name, value) < 0){ + if (cvec_add_string(sd->sd_outp_hdrs, (char*)name, value) < 0){ clicon_err(OE_RESTCONF, errno, "cvec_add_string"); goto done; } @@ -129,130 +124,60 @@ restconf_reply_header(void *req0, if (value) free(value); return retval; -#else /* HAVE_LIBEVHTP */ - return 0; -#endif /* HAVE_LIBEVHTP */ } -#ifdef HAVE_LIBEVHTP -/*! Send reply - * @see htp__create_reply_ - */ -#define rc_parser conn->parser /* XXX */ -static int -native_send_reply(restconf_conn_h *rc, - evhtp_request_t *request, - evhtp_res code) -{ - int retval = -1; - unsigned char major; - unsigned char minor; - cg_var *cv; - - switch (request->proto) { - case EVHTP_PROTO_10: - if (request->flags & EVHTP_REQ_FLAG_KEEPALIVE) { - /* protocol is HTTP/1.0 and clients wants to keep established */ - if (restconf_reply_header(request, "Connection", "keep-alive") < 0) - goto done; - } - major = htparser_get_major(request->rc_parser); /* XXX Look origin */ - minor = htparser_get_minor(request->rc_parser); - break; - case EVHTP_PROTO_11: - if (!(request->flags & EVHTP_REQ_FLAG_KEEPALIVE)) { - /* protocol is HTTP/1.1 but client wanted to close */ - if (restconf_reply_header(request, "Connection", "keep-alive") < 0) - goto done; - } - major = htparser_get_major(request->rc_parser); - minor = htparser_get_minor(request->rc_parser); - break; - default: - /* this sometimes happens when a response is made but paused before - * the method has been parsed */ - major = 1; - minor = 0; - break; - } - - cprintf(rc->rc_outp_buf, "HTTP/%u.%u %u %s\r\n", major, minor, code, restconf_code2reason(code)); - - /* Loop over headers */ - cv = NULL; - while ((cv = cvec_each(rc->rc_outp_hdrs, cv)) != NULL) - cprintf(rc->rc_outp_buf, "%s: %s\r\n", cv_name_get(cv), cv_string_get(cv)); - cprintf(rc->rc_outp_buf, "\r\n"); - // cvec_reset(rc->rc_outp_hdrs); /* Is now done in restconf_connection but can be done here */ - retval = 0; - done: - return retval; -} -#endif /* HAVE_LIBEVHTP */ - /*! Send HTTP reply with potential message body - * @param[in] req Evhtp http request handle - * @param[in] cb Body as a cbuf, send if + * @param[in] req http request handle + * @param[in] cb Body as a cbuf if non-NULL. Note is consumed + * @param[in] head Only send headers, dont send body. * * Prerequisites: status code set, headers given, body if wanted set */ int restconf_reply_send(void *req0, int code, - cbuf *cb) + cbuf *cb, + int head) { -#ifdef HAVE_LIBEVHTP - evhtp_request_t *req = (evhtp_request_t *)req0; - int retval = -1; - const char *reason_phrase; - evhtp_connection_t *conn; - restconf_conn_h *rc; + int retval = -1; + restconf_stream_data *sd = (restconf_stream_data *)req0; clicon_debug(1, "%s code:%d", __FUNCTION__, code); - req->status = code; - if ((reason_phrase = restconf_code2reason(code)) == NULL) - reason_phrase=""; -#if 0 /* XXX remove status header for evhtp? */ - if (restconf_reply_header(req, "Status", "%d %s", code, reason_phrase) < 0) + if (sd == NULL){ + clicon_err(OE_CFG, EINVAL, "sd is NULL"); goto done; + } + sd->sd_code = code; + if (cb != NULL){ + if (cbuf_len(cb)){ + cprintf(cb, "\r\n"); +#if 0 /* Double for evhtp, single for nghttp2 */ + if (restconf_reply_header(sd, "Content-Length", "%d", cbuf_len(cb)) < 0) + goto done; #endif - if ((conn = evhtp_request_get_connection(req)) == NULL){ - clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection"); - goto done; - } - /* If body, add a content-length header - * A server MUST NOT send a Content-Length header field in any response - * with a status code of 1xx (Informational) or 204 (No Content). A - * server MUST NOT send a Content-Length header field in any 2xx - * (Successful) response to a CONNECT request (Section 4.3.6 of - * [RFC7231]). - */ - if (cb != NULL && cbuf_len(cb)){ - cprintf(cb, "\r\n"); - if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0) - goto done; + if (head) + cbuf_free(cb); + else{ + sd->sd_body = cb; + sd->sd_body_offset = 0; + } + } + else{ + cbuf_free(cb); +#if 0 + if (restconf_reply_header(sd, "Content-Length", "0") < 0) + goto done; +#endif + } } +#if 0 else - if (restconf_reply_header(req, "Content-Length", "0") < 0) - goto done; - if ((rc = conn->arg) == NULL){ - clicon_err(OE_RESTCONF, EFAULT, "Internal error: restconf-conn-h is NULL: shouldnt happen"); - goto done; - } - /* Create reply and write headers */ - if (native_send_reply(rc, req, code) < 0) - goto done; - req->flags |= EVHTP_REQ_FLAG_FINISHED; /* Signal to evhtp to read next request */ - /* Write a body if cbuf is nonzero */ - if (cb != NULL && cbuf_len(cb)){ - cprintf(rc->rc_outp_buf, "%s", cbuf_get(cb)); - } + if (restconf_reply_header(sd, "Content-Length", "0") < 0) + goto done; +#endif retval = 0; done: return retval; -#else /* HAVE_LIBEVHTP */ - return 0; -#endif /* HAVE_LIBEVHTP */ } /*! get input data @@ -262,27 +187,15 @@ restconf_reply_send(void *req0, cbuf * restconf_get_indata(void *req0) { - cbuf *cb = NULL; -#ifdef HAVE_LIBEVHTP - evhtp_request_t *req = (evhtp_request_t *)req0; - - size_t len; - unsigned char *buf; - - if ((cb = cbuf_new()) == NULL) - return NULL; - len = evbuffer_get_length(req->buffer_in); - if (len > 0){ - if ((buf = evbuffer_pullup(req->buffer_in, len)) == NULL){ - clicon_err(OE_CFG, errno, "evbuffer_pullup"); - return NULL; - } - /* Note the pullup may not be null-terminated */ - cbuf_append_buf(cb, buf, len); + restconf_stream_data *sd = (restconf_stream_data *)req0; + cbuf *cb = NULL; + + if (sd == NULL){ + clicon_err(OE_CFG, EINVAL, "sd is NULL"); + goto done; } + cb = sd->sd_indata; + done: return cb; -#else /* HAVE_LIBEVHTP */ - return cb; -#endif /* HAVE_LIBEVHTP */ } diff --git a/apps/restconf/restconf_err.c b/apps/restconf/restconf_err.c index efe467f4..1a7aaee3 100644 --- a/apps/restconf/restconf_err.c +++ b/apps/restconf/restconf_err.c @@ -147,7 +147,7 @@ restconf_not_acceptable(clicon_handle h, /* Override with 415 netconf->restoconf translation which gives a 405 */ if (api_return_err0(h, req, xerr, pretty, media, 415) < 0) goto done; - if (restconf_reply_send(req, 415, NULL) < 0) + if (restconf_reply_send(req, 415, NULL, 0) < 0) goto done; retval = 0; done: @@ -301,8 +301,9 @@ api_return_err(clicon_handle h, break; } /* switch media */ assert(cbuf_len(cb)); - if (restconf_reply_send(req, code, cb) < 0) + if (restconf_reply_send(req, code, cb, 0) < 0) goto done; + cb = NULL; // ok: retval = 0; done: diff --git a/apps/restconf/restconf_evhtp.c b/apps/restconf/restconf_evhtp.c index ef376edb..09acd04f 100644 --- a/apps/restconf/restconf_evhtp.c +++ b/apps/restconf/restconf_evhtp.c @@ -54,6 +54,8 @@ #include #include +#include + /* cligen */ #include @@ -61,13 +63,12 @@ #include #ifdef HAVE_LIBEVHTP -/* evhtp */ #include /* evbuffer */ #define EVHTP_DISABLE_REGEX #define EVHTP_DISABLE_EVTHR #include -#include /* XXX inline this / use SSL directly */ + #endif /* HAVE_LIBEVHTP */ #ifdef HAVE_LIBNGHTTP2 /* To get restconf_native.h include files right */ @@ -81,7 +82,9 @@ #include "restconf_err.h" #include "restconf_root.h" #include "restconf_native.h" /* Restconf-openssl mode specific headers*/ +#ifdef HAVE_LIBEVHTP #include "restconf_evhtp.h" /* evhtp http/1 */ +#endif #ifdef HAVE_LIBEVHTP static char* @@ -264,15 +267,19 @@ evhtp_params_set(clicon_handle h, goto fail; } clicon_debug(1, "%s conn->ssl:%d", __FUNCTION__, req->conn->ssl?1:0); + /* Slightly awkward way of taking cert subject and CN and add it to restconf parameters + * instead of accessing it directly */ if ((ssl = req->conn->ssl) != NULL){ if (restconf_param_set(h, "HTTPS", "https") < 0) /* some string or NULL */ goto done; /* SSL subject fields, eg CN (Common Name) , can add more here? */ - if ((subject = (char*)htp_sslutil_subject_tostr(req->conn->ssl)) != NULL){ + if (ssl_x509_name_oneline(req->conn->ssl, &subject) < 0) + goto done; + if (subject != NULL) { if (uri_str2cvec(subject, '/', '=', 1, &cvv) < 0) goto done; if ((cn = cvec_find_str(cvv, "CN")) != NULL){ - if (restconf_param_set(h, "SSL_CN", cn) < 0) + if (restconf_param_set(h, "SSL_CN", cn) < 0) /* Can be used by callback */ goto done; } } @@ -298,6 +305,111 @@ evhtp_params_set(clicon_handle h, goto done; } +/*! We got -1 back from lower layers, create a 500 Internal Server error + * Catch all on fatal error. This does not terminate the process but closes request + * stream + */ +static int +evhtp_internal_error(evhtp_request_t *req) +{ + if (strlen(clicon_err_reason) && + req->buffer_out){ + evbuffer_add_printf(req->buffer_out, "%s", clicon_err_reason); + evhtp_send_reply(req, EVHTP_RES_500); + } + clicon_err_reset(); + return 0; +} + +/*! Send reply + * @see htp__create_reply_ + */ +static int +native_send_reply(restconf_conn *rc, + restconf_stream_data *sd, + evhtp_request_t *req) +{ + int retval = -1; + + cg_var *cv; + int minor; + int major; + + switch (req->proto) { + case EVHTP_PROTO_10: + if (req->flags & EVHTP_REQ_FLAG_KEEPALIVE) { + /* protocol is HTTP/1.0 and clients wants to keep established */ + if (restconf_reply_header(sd, "Connection", "keep-alive") < 0) + goto done; + } + major = htparser_get_major(req->conn->parser); /* XXX Look origin */ + minor = htparser_get_minor(req->conn->parser); + break; + case EVHTP_PROTO_11: + if (!(req->flags & EVHTP_REQ_FLAG_KEEPALIVE)) { + /* protocol is HTTP/1.1 but client wanted to close */ + if (restconf_reply_header(sd, "Connection", "keep-alive") < 0) + goto done; + } + major = htparser_get_major(req->conn->parser); + minor = htparser_get_minor(req->conn->parser); + break; + default: + /* this sometimes happens when a response is made but paused before + * the method has been parsed */ + major = 1; + minor = 0; + break; + } + cprintf(sd->sd_outp_buf, "HTTP/%u.%u %u %s\r\n", + major, + minor, + sd->sd_code, + restconf_code2reason(sd->sd_code)); + /* Loop over headers */ + cv = NULL; + while ((cv = cvec_each(sd->sd_outp_hdrs, cv)) != NULL) + cprintf(sd->sd_outp_buf, "%s: %s\r\n", cv_name_get(cv), cv_string_get(cv)); + cprintf(sd->sd_outp_buf, "\r\n"); + // cvec_reset(rc->rc_outp_hdrs); /* Is now done in restconf_connection but can be done here */ + retval = 0; + done: + return retval; +} + +/*! + */ +static int +restconf_evhtp_reply(restconf_conn *rc, + restconf_stream_data *sd, + evhtp_request_t *req) +{ + int retval = -1; + + req->status = sd->sd_code; + req->flags |= EVHTP_REQ_FLAG_FINISHED; /* Signal to evhtp to read next request */ + /* If body, add a content-length header + * A server MUST NOT send a Content-Length header field in any response + * with a status code of 1xx (Informational) or 204 (No Content). A + * server MUST NOT send a Content-Length header field in any 2xx + * (Successful) response to a CONNECT request (Section 4.3.6 of + * [RFC7231]). + */ + if (restconf_reply_header(sd, "Content-Length", "%d", + (sd->sd_body!=NULL)?cbuf_len(sd->sd_body):0) < 0) + goto done; + /* Create reply and write headers */ + if (native_send_reply(rc, sd, req) < 0) + goto done; + /* Write a body */ + if (sd->sd_body){ + cbuf_append_str(sd->sd_outp_buf, cbuf_get(sd->sd_body)); + } + retval = 0; + done: + return retval; +} + /*! Callback for each incoming http request for path / * * This are all messages except /.well-known, Registered with evhtp_set_cb @@ -314,16 +426,35 @@ void restconf_path_root(evhtp_request_t *req, void *arg) { - int retval = -1; - clicon_handle h; - int ret; - cvec *qvec = NULL; + int retval = -1; + clicon_handle h; + int ret; + cvec *qvec = NULL; + evhtp_connection_t *evconn; + restconf_conn *rc; + restconf_stream_data *sd; clicon_debug(1, "------------"); if ((h = (clicon_handle)arg) == NULL){ clicon_err(OE_RESTCONF, EINVAL, "arg is NULL"); + evhtp_internal_error(req); goto done; } + /* evhtp connect struct */ + if ((evconn = evhtp_request_get_connection(req)) == NULL){ + clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection"); + evhtp_internal_error(req); + goto done; + } + /* get clixon request connect pointer from generic evhtp application pointer */ + rc = evconn->arg; + if ((sd = restconf_stream_find(rc, 0)) == NULL){ + clicon_err(OE_RESTCONF, EINVAL, "No stream_data"); + evhtp_internal_error(req); + goto done; + } + sd->sd_req = req; + sd->sd_proto = (req->proto == EVHTP_PROTO_10)?HTTP_10:HTTP_11; /* input debug */ if (clicon_debug_get()) evhtp_headers_for_each(req->headers_in, evhtp_print_header, h); @@ -332,27 +463,55 @@ restconf_path_root(evhtp_request_t *req, /* Query vector, ie the ?a=x&b=y stuff */ if ((qvec = cvec_new(0)) ==NULL){ clicon_err(OE_UNIX, errno, "cvec_new"); + evhtp_internal_error(req); goto done; } - /* set fcgi-like paramaters (ignore query vector) */ - if ((ret = evhtp_params_set(h, req, qvec)) < 0) + /* Get indata + */ + { + size_t len; + unsigned char *buf; + + len = evbuffer_get_length(req->buffer_in); + if (len > 0){ + if ((buf = evbuffer_pullup(req->buffer_in, len)) == NULL){ + clicon_err(OE_CFG, errno, "evbuffer_pullup"); + goto done; + } + /* Note the pullup may not be null-terminated */ + cbuf_append_buf(sd->sd_indata, buf, len); + } + } + /* set fcgi-like paramaters (ignore query vector) + * ret = 0 means an error has already been sent + */ + if ((ret = evhtp_params_set(h, req, qvec)) < 0){ + evhtp_internal_error(req); goto done; + } if (ret == 1){ /* call generic function */ - if (api_root_restconf(h, req, qvec) < 0) - goto done; + if (api_root_restconf(h, sd, qvec) < 0){ + evhtp_internal_error(req); + goto done; + } } - - /* Clear (fcgi) paramaters from this request */ - if (restconf_param_del_all(h) < 0) + /* Clear input request parameters from this request */ + if (restconf_param_del_all(h) < 0){ + evhtp_internal_error(req); goto done; + } + /* All parameters for sending a reply are here + */ + if (sd->sd_code){ + if (restconf_evhtp_reply(rc, sd, req) < 0){ + evhtp_internal_error(req); + goto done; + } + } retval = 0; done: clicon_debug(1, "%s %d", __FUNCTION__, retval); - /* Catch all on fatal error. This does not terminate the process but closes request stream */ - if (retval < 0){ - evhtp_send_reply(req, EVHTP_RES_ERROR); - } if (qvec) cvec_free(qvec); return; /* void */ @@ -368,37 +527,67 @@ void restconf_path_wellknown(evhtp_request_t *req, void *arg) { - int retval = -1; - clicon_handle h; - int ret; + int retval = -1; + clicon_handle h; + int ret; + evhtp_connection_t *evconn; + restconf_conn *rc; + restconf_stream_data *sd; clicon_debug(1, "------------"); if ((h = (clicon_handle)arg) == NULL){ clicon_err(OE_RESTCONF, EINVAL, "arg is NULL"); + evhtp_internal_error(req); goto done; } + /* evhtp connect struct */ + if ((evconn = evhtp_request_get_connection(req)) == NULL){ + clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection"); + evhtp_internal_error(req); + goto done; + } + /* get clixon request connect pointer from generic evhtp application pointer */ + rc = evconn->arg; + if ((sd = restconf_stream_find(rc, 0)) == NULL){ + clicon_err(OE_RESTCONF, EINVAL, "No stream_data"); + evhtp_internal_error(req); + goto done; + } + sd->sd_req = req; + sd->sd_proto = (req->proto == EVHTP_PROTO_10)?HTTP_10:HTTP_11; /* input debug */ if (clicon_debug_get()) evhtp_headers_for_each(req->headers_in, evhtp_print_header, h); /* get accepted connection */ /* set fcgi-like paramaters (ignore query vector) */ - if ((ret = evhtp_params_set(h, req, NULL)) < 0) + if ((ret = evhtp_params_set(h, req, NULL)) < 0){ + evhtp_internal_error(req); goto done; + } if (ret == 1){ /* call generic function */ - if (api_well_known(h, req) < 0) + if (api_well_known(h, sd) < 0){ + evhtp_internal_error(req); goto done; + } } - /* Clear (fcgi) paramaters from this request */ - if (restconf_param_del_all(h) < 0) + /* Clear input request parameters from this request */ + if (restconf_param_del_all(h) < 0){ + evhtp_internal_error(req); goto done; + } + /* All parameters for sending a reply are here + */ + if (sd->sd_code){ + if (restconf_evhtp_reply(rc, sd, req) < 0){ + evhtp_internal_error(req); + goto done; + } + } retval = 0; done: - /* Catch all on fatal error. This does not terminate the process but closes request stream */ - if (retval < 0){ - evhtp_send_reply(req, EVHTP_RES_ERROR); - } + clicon_debug(1, "%s %d", __FUNCTION__, retval); return; /* void */ } #endif /* HAVE_LIBEVHTP */ diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 8a979fd0..9b0592c4 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -530,24 +530,30 @@ restconf_main_extension_cb(clicon_handle h, return retval; } -/*! Extract uri-encoded uri-path from fastcgi parameters +/*! Extract uri-encoded uri-path without arguments + * * Use REQUEST_URI parameter and strip ?args - * REQUEST_URI have args and is encoded - * eg /interface=eth%2f0%2f0?insert=first - * DOCUMENT_URI dont have args and is not encoded - * eg /interface=eth/0/0 - * causes problems with eg /interface=eth%2f0%2f0 + * eg /interface=eth%2f0%2f0?insert=first -> /interface=eth%2f0%2f0 + * @retval path malloced, need free */ char * restconf_uripath(clicon_handle h) { - char *path; + char *path = NULL; + char *path2 = NULL; char *q; - path = restconf_param_get(h, "REQUEST_URI"); - if ((q = index(path, '?')) != NULL) + if ((path = restconf_param_get(h, "REQUEST_URI")) == NULL){ + clicon_err(OE_RESTCONF, 0, "No REQUEST_URI"); + return NULL; + } + if ((path2 = strdup(path)) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + return NULL; + } + if ((q = index(path2, '?')) != NULL) *q = '\0'; - return path; + return path2; } /*! Drop privileges from root to user (or already at user) @@ -936,3 +942,4 @@ restconf_socket_extract(clicon_handle h, free(reason); return retval; } + diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c index 01483b8a..c03cbe6a 100644 --- a/apps/restconf/restconf_main_native.c +++ b/apps/restconf/restconf_main_native.c @@ -158,7 +158,7 @@ #define EVHTP_DISABLE_EVTHR #include -#include /* XXX inline this / use SSL directly */ + #endif /* HAVE_LIBEVHTP */ #ifdef HAVE_LIBNGHTTP2 @@ -266,6 +266,7 @@ buf_write(char *buf, memcpy(dbgstr, buf, sz); dbgstr[sz] = '\0'; clicon_debug(1, "%s buflen:%lu buf:%s", __FUNCTION__, buflen, dbgstr); + free(dbgstr); } while (totlen < buflen){ if (ssl){ @@ -392,7 +393,6 @@ restconf_verify_certs(int preverify_ok, // SSL *ssl; // clicon_handle h; - clicon_debug(1, "%s %d", __FUNCTION__, preverify_ok); err_cert = X509_STORE_CTX_get_current_cert(store); err = X509_STORE_CTX_get_error(store); depth = X509_STORE_CTX_get_error_depth(store); @@ -406,14 +406,13 @@ restconf_verify_certs(int preverify_ok, break; } /* Catch a too long certificate chain. should be +1 in SSL_CTX_set_verify_depth() */ - if (depth > 1) { + if (depth > VERIFY_DEPTH + 1) { preverify_ok = 0; err = X509_V_ERR_CERT_CHAIN_TOO_LONG; X509_STORE_CTX_set_error(store, err); } - if (depth == VERIFY_DEPTH){ + else{ /* Verify the CA name */ - } // h = SSL_get_app_data(ssl); return preverify_ok; @@ -422,28 +421,25 @@ restconf_verify_certs(int preverify_ok, /*! Debug print of all incoming alpn alternatives, eg h2 and http/1.1 */ static int -dump_alpn_proto_list(const unsigned char *in, - unsigned int inlen) +alpn_proto_dump(const char *label, + const char *inp, + int len) { - unsigned char *inp; - unsigned char len; - char *str; - inp = (unsigned char*)in; - while ((inp-in) < inlen) { - len = *inp; - inp++; - if ((str = malloc(len+1)) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); - return -1; - } - strncpy(str, (const char*)inp, len); - str[len] = '\0'; - clicon_debug(1, "%s %s", __FUNCTION__, str); - free(str); - inp += len; + int retval = -1; + char *str = NULL; + + if ((str = malloc(len+1)) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; } - return 0; + strncpy(str, inp, len); + str[len] = '\0'; + clicon_debug(1, "%s %s", label, str); + retval = 0; + done: + free(str); + return retval; } /*! Application-layer Protocol Negotiation (alpn) callback @@ -464,13 +460,13 @@ alpn_select_proto_cb(SSL *ssl, int pref = 0; clicon_debug(1, "%s", __FUNCTION__); - if (clicon_debug_get()) - dump_alpn_proto_list(in, inlen); /* select http/1.1 */ inp = (unsigned char*)in; while ((inp-in) < inlen) { len = *inp; inp++; + if (clicon_debug_get()) /* debug print the protoocol */ + alpn_proto_dump(__FUNCTION__, (const char*)inp, len); if (pref < 10 && len == 8 && strncmp((char*)inp, "http/1.1", len) == 0){ *outlen = len; *out = inp; @@ -488,6 +484,7 @@ alpn_select_proto_cb(SSL *ssl, } if (pref == 0) return SSL_TLSEXT_ERR_NOACK; + alpn_proto_dump("ALPN selected:", (const char*)*out, *outlen); return SSL_TLSEXT_ERR_OK; } @@ -584,29 +581,13 @@ restconf_ssl_context_configure(clixon_handle h, return retval; } -/*! Free clixon/cbuf resources related to an evhtp connection - * @param[in] rc restconf connection - */ -static int -restconf_conn_free(restconf_conn_h *rc) -{ - if (rc != NULL){ - if (rc->rc_outp_hdrs) - cvec_free(rc->rc_outp_hdrs); - if (rc->rc_outp_buf) - cbuf_free(rc->rc_outp_buf); - free(rc); - } - return 0; -} - /*! Utility function to close restconf server ssl/evhtp socket. * There are many variants to closing, one could probably make this more generic * and always use this function, but it is difficult. */ static int -close_ssl_socket(restconf_conn_h *rc, - int shutdown) +restconf_close_ssl_socket(restconf_conn *rc, + int shutdown) { int retval = -1; int ret; @@ -619,6 +600,10 @@ close_ssl_socket(restconf_conn_h *rc, #endif /* HAVE_LIBEVHTP */ if (rc->rc_ssl != NULL){ if (shutdown && (ret = SSL_shutdown(rc->rc_ssl)) < 0){ +#if 0 + case SSL_ERROR_ZERO_RETURN: /* 6 */ +Note that in this case SSL_ERROR_ZERO_RETURN does not necessarily indicate that the underlying transport has been closed. +#endif int e = SSL_get_error(rc->rc_ssl, ret); clicon_err(OE_SSL, 0, "SSL_shutdown, err:%d", e); goto done; @@ -631,7 +616,6 @@ close_ssl_socket(restconf_conn_h *rc, goto done; } clixon_event_unreg_fd(rc->rc_s, restconf_connection); - restconf_conn_free(rc); retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); @@ -696,18 +680,19 @@ static int restconf_connection(int s, void *arg) { - int retval = -1; - restconf_conn_h *rc = NULL; - ssize_t n; - char buf[BUFSIZ]; /* from stdio.h, typically 8K XXX: reduce for test */ - int readmore = 1; + int retval = -1; + restconf_conn *rc = NULL; + ssize_t n; + char buf[BUFSIZ]; /* from stdio.h, typically 8K XXX: reduce for test */ + int readmore = 1; #ifdef HAVE_LIBEVHTP - clicon_handle h; - evhtp_connection_t *evconn = NULL; + clicon_handle h; + evhtp_connection_t *evconn = NULL; + restconf_stream_data *sd; #endif clicon_debug(1, "%s %d", __FUNCTION__, s); - if ((rc = (restconf_conn_h*)arg) == NULL){ + if ((rc = (restconf_conn*)arg) == NULL){ clicon_err(OE_RESTCONF, EINVAL, "arg is NULL"); goto done; } @@ -741,93 +726,107 @@ restconf_connection(int s, clicon_debug(1, "%s read:%ld", __FUNCTION__, n); if (n == 0){ clicon_debug(1, "%s n=0 closing socket", __FUNCTION__); - if (close_ssl_socket(rc, 1) < 0) + if (restconf_close_ssl_socket(rc, 1) < 0) goto done; + restconf_conn_free(rc); + rc = NULL; goto ok; } switch (rc->rc_proto){ #ifdef HAVE_LIBEVHTP case HTTP_10: case HTTP_11: - h = rc->rc_h; - /* parse incoming packet using evhtp - * signature: - */ - evconn = rc->rc_evconn; - if (connection_parse_nobev(buf, n, evconn) < 0){ - clicon_debug(1, "%s connection_parse error", __FUNCTION__); - /* XXX To get more nuanced evhtp error check - * htparser_get_error(conn->parser) + h = rc->rc_h; + /* parse incoming packet using evhtp + * signature: */ - if (send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml", - "protocolmalformed-messageThe requested URL or a header is in some way badly formed") < 0) - goto done; - SSL_free(rc->rc_ssl); - rc->rc_ssl = NULL; - evconn->ssl = NULL; - if (close(rc->rc_s) < 0){ - clicon_err(OE_UNIX, errno, "close"); + evconn = rc->rc_evconn; + /* This is the main call to EVHTP parser */ + if (connection_parse_nobev(buf, n, evconn) < 0){ + clicon_debug(1, "%s connection_parse error", __FUNCTION__); + /* XXX To get more nuanced evhtp error check + * htparser_get_error(conn->parser) + */ + if (send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml", + "protocolmalformed-messageThe requested URL or a header is in some way badly formed") < 0) + goto done; + SSL_free(rc->rc_ssl); + rc->rc_ssl = NULL; + evconn->ssl = NULL; + if (close(rc->rc_s) < 0){ + clicon_err(OE_UNIX, errno, "close"); + goto done; + } + clixon_event_unreg_fd(rc->rc_s, restconf_connection); + clicon_debug(1, "%s evconn-free (%p) 2", __FUNCTION__, evconn); + restconf_conn_free(rc); + evhtp_connection_free(evconn); + goto ok; + } + clicon_debug(1, "%s connection_parse OK", __FUNCTION__); + /* default stream */ + if ((sd = restconf_stream_find(rc, 0)) == NULL){ + clicon_err(OE_RESTCONF, EINVAL, "restconf stream not found"); goto done; } - clixon_event_unreg_fd(rc->rc_s, restconf_connection); - clicon_debug(1, "%s evconn-free (%p) 2", __FUNCTION__, evconn); - restconf_conn_free(rc); - evhtp_connection_free(evconn); - goto ok; - } - clicon_debug(1, "%s connection_parse OK", __FUNCTION__); - if (evconn->bev != NULL){ - struct evbuffer *ev; - size_t buflen0; - size_t buflen1; - char *buf = NULL; + if (evconn->bev != NULL){ + struct evbuffer *ev; + size_t buflen0; + size_t buflen1; + char *buf = NULL; - if ((ev = bufferevent_get_output(evconn->bev)) != NULL){ - buflen0 = evbuffer_get_length(ev); - buflen1 = buflen0 - rc->rc_bufferevent_output_offset; - if (buflen1 > 0){ - buf = (char*)evbuffer_pullup(ev, -1); - /* If evhtp has print an output buffer, clixon whould not have done it - * Shouldnt happen - */ - if (cbuf_len(rc->rc_outp_buf)){ - clicon_debug(1, "%s Warning: evhtp printed output buffer, but clixon output buffer is non-empty %s", - __FUNCTION__, cbuf_get(rc->rc_outp_buf)); - cbuf_reset(rc->rc_outp_buf); + if ((ev = bufferevent_get_output(evconn->bev)) != NULL){ + buflen0 = evbuffer_get_length(ev); + buflen1 = buflen0 - rc->rc_bufferevent_output_offset; + if (buflen1 > 0){ + buf = (char*)evbuffer_pullup(ev, -1); + /* XXX Here if -1 in api_root + * HTTP/1.1 0 UNKNOWN\r\nContent-Length: 0 + * And output_buffer is NULL + */ + /* If evhtp has print an output buffer, clixon whould not have done it + * Shouldnt happen + */ + if (cbuf_len(sd->sd_outp_buf)){ + clicon_debug(1, "%s Warning: evhtp printed output buffer, but clixon output buffer is non-empty %s", + __FUNCTION__, cbuf_get(sd->sd_outp_buf)); + cbuf_reset(sd->sd_outp_buf); + } + if (cbuf_append_buf(sd->sd_outp_buf, buf, buflen1) < 0){ + clicon_err(OE_UNIX, errno, "cbuf_append_buf"); + goto done; + } + /* XXX Cant get drain to work, need to keep an offset */ + evbuffer_drain(ev, -1); + rc->rc_bufferevent_output_offset += buflen1; } - if (cbuf_append_buf(rc->rc_outp_buf, buf, buflen1) < 0){ - clicon_err(OE_UNIX, errno, "cbuf_append_buf"); + } + if (cbuf_len(sd->sd_outp_buf) == 0) + readmore = 1; + else { + if (buf_write(cbuf_get(sd->sd_outp_buf), cbuf_len(sd->sd_outp_buf), + rc->rc_s, rc->rc_ssl) < 0) goto done; - } - /* XXX Cant get drain to work, need to keep an offset */ - evbuffer_drain(ev, -1); - rc->rc_bufferevent_output_offset += buflen1; + cvec_reset(sd->sd_outp_hdrs); /* Can be done in native_send_reply */ + cbuf_reset(sd->sd_outp_buf); } } - if (cbuf_len(rc->rc_outp_buf) == 0) - readmore = 1; - else { - if (buf_write(cbuf_get(rc->rc_outp_buf), cbuf_len(rc->rc_outp_buf), - rc->rc_s, rc->rc_ssl) < 0) + else{ + if (send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml", + "protocolmalformed-messageNo evhtp output") < 0) goto done; - cvec_reset(rc->rc_outp_hdrs); /* Can be done in native_send_reply */ - cbuf_reset(rc->rc_outp_buf); } - } - else{ - if (send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml", - "protocolmalformed-messageNo evhtp output") < 0) - goto done; - } + break; #endif /* HAVE_LIBEVHTP */ #ifdef HAVE_LIBNGHTTP2 case HTTP_2: if (http2_recv(rc, (unsigned char *)buf, n) < 0) goto done; + // notused sd = restconf_stream_find(rc, 0); /* default stream */ break; #endif /* HAVE_LIBNGHTTP2 */ - default: - break; + default: + break; } /* switch rc_proto */ } /* while readmore */ ok: @@ -913,7 +912,7 @@ static int ssl_alpn_check(clicon_handle h, const unsigned char *alpn, unsigned int alpnlen, - restconf_conn_h *rc, + restconf_conn *rc, restconf_http_proto *proto) { int retval = -1; @@ -998,7 +997,7 @@ restconf_accept_client(int fd, int retval = -1; restconf_socket *rsock; restconf_native_handle *rh = NULL; - restconf_conn_h *rc = NULL; + restconf_conn *rc = NULL; clicon_handle h; int s; struct sockaddr from = {0,}; @@ -1008,12 +1007,15 @@ restconf_accept_client(int fd, int e; int er; int readmore; - X509 *peercert; const unsigned char *alpn = NULL; unsigned int alpnlen = 0; - restconf_http_proto proto = HTTP_11; /* Non-SSL negotiation NYI */ + restconf_http_proto proto = HTTP_11; /* Non-SSL negotiation NYI */ clicon_debug(1, "%s %d", __FUNCTION__, fd); +#ifdef HAVE_LIBNGHTTP2 + /* If nghttp2 let default be 2.0 NOTE http protocol negotiation */ + proto = HTTP_2; +#endif if ((rsock = (restconf_socket *)arg) == NULL){ clicon_err(OE_YANG, EINVAL, "rsock is NULL"); goto done; @@ -1035,22 +1037,9 @@ restconf_accept_client(int fd, /* * Register callbacks for actual data socket */ - if ((rc = (restconf_conn_h*)malloc(sizeof(restconf_conn_h))) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); + if ((rc = restconf_conn_new(h, s)) == NULL) goto done; - } - memset(rc, 0, sizeof(restconf_conn_h)); - rc->rc_h = h; - rc->rc_s = s; clicon_debug(1, "%s s:%d", __FUNCTION__, rc->rc_s); - if ((rc->rc_outp_hdrs = cvec_new(0)) == NULL){ - clicon_err(OE_UNIX, errno, "cvec_new"); - goto done; - } - if ((rc->rc_outp_buf = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } if (rsock->rs_ssl){ if ((rc->rc_ssl = SSL_new(rh->rh_ctx)) == NULL){ clicon_err(OE_SSL, 0, "SSL_new"); @@ -1120,8 +1109,10 @@ restconf_accept_client(int fd, operations should be performed on the connection and SSL_shutdown() must not be called.*/ clicon_debug(1, "%s SSL_accept() SSL_ERROR_SYSCALL %d", __FUNCTION__, er); - if (close_ssl_socket(rc, 0) < 0) + if (restconf_close_ssl_socket(rc, 0) < 0) goto done; + restconf_conn_free(rc); + rc = NULL; goto ok; break; case SSL_ERROR_WANT_READ: /* 2 */ @@ -1152,17 +1143,33 @@ restconf_accept_client(int fd, } } /* SSL_accept */ } /* while(readmore) */ + /* Sets data and len to point to the client's requested protocol for this connection. */ + SSL_get0_next_proto_negotiated(rc->rc_ssl, &alpn, &alpnlen); + if (alpn == NULL) { + /* Returns a pointer to the selected protocol in data with length len. */ + SSL_get0_alpn_selected(rc->rc_ssl, &alpn, &alpnlen); + } + if ((ret = ssl_alpn_check(h, alpn, alpnlen, rc, &proto)) < 0) + goto done; + if (ret == 0) + goto ok; + clicon_debug(1, "%s proto:%s", __FUNCTION__, restconf_proto2str(proto)); + +#if 0 /* Seems too early to fail here, instead let authentication callback deal with this */ /* For client-cert authentication, check if any certs are present, * if not, send bad request * Alt: set SSL_CTX_set_verify(ctx, SSL_VERIFY_FAIL_IF_NO_PEER_CERT) * but then SSL_accept fails. */ if (restconf_auth_type_get(h) == CLIXON_AUTH_CLIENT_CERTIFICATE){ + X509 *peercert; + if ((peercert = SSL_get_peer_certificate(rc->rc_ssl)) != NULL){ X509_free(peercert); } else { /* Get certificates (if available) */ - if (send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml", + if (proto != HTTP_2 && + send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml", "protocolmalformed-messagePeer certificate required") < 0) goto done; restconf_conn_free(rc); @@ -1178,17 +1185,7 @@ restconf_accept_client(int fd, goto ok; } } - /* Sets data and len to point to the client's requested protocol for this connection. */ - SSL_get0_next_proto_negotiated(rc->rc_ssl, &alpn, &alpnlen); - if (alpn == NULL) { - /* Returns a pointer to the selected protocol in data with length len. */ - SSL_get0_alpn_selected(rc->rc_ssl, &alpn, &alpnlen); - } - if ((ret = ssl_alpn_check(h, alpn, alpnlen, rc, &proto)) < 0) - goto done; - if (ret == 0) - goto ok; - clicon_debug(1, "%s proto:%s", __FUNCTION__, restconf_proto2str(proto)); +#endif /* Get the actual peer, XXX this maybe could be done in ca-auth client-cert code ? * Note this _only_ works if SSL_set1_host() was set previously,... */ @@ -1196,7 +1193,7 @@ restconf_accept_client(int fd, const char *peername = SSL_get0_peername(rc->rc_ssl); - if (peername != NULL) { + if (peername != NULL) { /* Name checks were in scope and matched the peername */ clicon_debug(1, "%s peername:%s", __FUNCTION__, peername); } @@ -1224,13 +1221,36 @@ restconf_accept_client(int fd, rc->rc_evconn = evconn; /* Generic to specific */ evconn->arg = rc; /* Specific to generic */ evconn->ssl = rc->rc_ssl; /* evhtp */ + /* Create a default stream for http/1 */ + if (restconf_stream_data_new(rc, 0) == NULL) + goto done; } break; #endif /* HAVE_LIBEVHTP */ #ifdef HAVE_LIBNGHTTP2 case HTTP_2:{ - if (http2_session_init(rc) < 0) + if (http2_session_init(rc) < 0){ + restconf_close_ssl_socket(rc, 1); goto done; + } + if (http2_send_server_connection(rc) < 0){ + restconf_close_ssl_socket(rc, 1); +#ifdef NYI + if (ssl) { + SSL_shutdown(ssl); + } + bufferevent_free(session_data->bev); + nghttp2_session_del(session_data->session); + for (stream_data = session_data->root.next; stream_data;) { + http2_stream_data *next = stream_data->next; + delete_http2_stream_data(stream_data); + stream_data = next; + } + free(session_data->client_addr); + free(session_data); +#endif + goto done; + } break; } #endif /* HAVE_LIBNGHTTP2 */ diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 26664316..40d8fd99 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -99,7 +99,7 @@ api_data_options(clicon_handle h, goto done; if (restconf_reply_header(req, "Accept-Patch", "application/yang-data+xml,application/yang-data+json") < 0) goto done; - if (restconf_reply_send(req, 200, NULL) < 0) + if (restconf_reply_send(req, 200, NULL, 0) < 0) goto done; retval = 0; done: @@ -547,11 +547,11 @@ api_data_write(clicon_handle h, if ((xe = xpath_first(xret, NULL, "//ok")) != NULL && (attr = xml_find_value(xe, "objectexisted")) != NULL && strcmp(attr, "false")==0){ - if (restconf_reply_send(req, 201, NULL) < 0) /* Created */ + if (restconf_reply_send(req, 201, NULL, 0) < 0) /* Created */ goto done; } else - if (restconf_reply_send(req, 204, NULL) < 0) /* No content */ + if (restconf_reply_send(req, 204, NULL, 0) < 0) /* No content */ goto done; ok: retval = 0; @@ -784,7 +784,7 @@ api_data_delete(clicon_handle h, goto done; goto ok; } - if (restconf_reply_send(req, 204, NULL) < 0) + if (restconf_reply_send(req, 204, NULL, 0) < 0) goto done; ok: retval = 0; diff --git a/apps/restconf/restconf_methods_get.c b/apps/restconf/restconf_methods_get.c index 4c6a254b..11aa644e 100644 --- a/apps/restconf/restconf_methods_get.c +++ b/apps/restconf/restconf_methods_get.c @@ -85,7 +85,7 @@ * Response contains one of: * Content-Type: application/yang-data+xml * Content-Type: application/yang-data+json - * NOTE: If a retrieval request for a data resource representing a YANG leaf- + * @note: If a retrieval request for a data resource representing a YANG leaf- * list or list object identifies more than one instance, and XML * encoding is used in the response, then an error response containing a * "400 Bad Request" status-line MUST be returned by the server. @@ -217,16 +217,6 @@ api_data_get2(clicon_handle h, /* Normal return, no error */ if ((cbx = cbuf_new()) == NULL) goto done; - if (head){ - /* Same headers as the GET, but no body */ - if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0) - goto done; - if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0) - goto done; - if (restconf_reply_send(req, 200, NULL) < 0) - goto done; - goto ok; - } if (xpath==NULL || strcmp(xpath,"/")==0){ /* Special case: data root */ switch (media_out){ case YANG_DATA_XML: @@ -295,8 +285,9 @@ api_data_get2(clicon_handle h, goto done; if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0) goto done; - if (restconf_reply_send(req, 200, cbx) < 0) + if (restconf_reply_send(req, 200, cbx, head) < 0) goto done; + cbx = NULL; ok: retval = 0; done: @@ -489,13 +480,13 @@ api_operations_get(clicon_handle h, default: break; } - if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0) goto done; if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0) goto done; - if (restconf_reply_send(req, 200, cbx) < 0) + if (restconf_reply_send(req, 200, cbx, 0) < 0) goto done; + cbx = NULL; // ok: retval = 0; done: diff --git a/apps/restconf/restconf_methods_post.c b/apps/restconf/restconf_methods_post.c index eddc210c..755f087f 100644 --- a/apps/restconf/restconf_methods_post.c +++ b/apps/restconf/restconf_methods_post.c @@ -84,7 +84,8 @@ http_location_header(clicon_handle h, https = restconf_param_get(h, "HTTPS"); host = restconf_param_get(h, "HTTP_HOST"); - request_uri = restconf_param_get(h, "REQUEST_URI"); + if ((request_uri = restconf_uripath(h)) == NULL) + goto done; if (xobj != NULL){ if ((cb = cbuf_new()) == NULL){ clicon_err(OE_UNIX, 0, "cbuf_new"); @@ -109,6 +110,8 @@ http_location_header(clicon_handle h, done: if (cb) cbuf_free(cb); + if (request_uri) + free(request_uri); return retval; } @@ -367,7 +370,7 @@ api_data_post(clicon_handle h, } if (http_location_header(h, req, xdata) < 0) goto done; - if (restconf_reply_send(req, 201, NULL) < 0) + if (restconf_reply_send(req, 201, NULL, 0) < 0) goto done; ok: retval = 0; @@ -621,7 +624,7 @@ api_operations_post_output(clicon_handle h, strcmp(xml_name(xok),"ok")==0); if (isempty) { /* Internal error - invalid output from rpc handler */ - if (restconf_reply_send(req, 204, NULL) < 0) + if (restconf_reply_send(req, 204, NULL, 0) < 0) goto done; goto fail; } @@ -872,8 +875,9 @@ api_operations_post(clicon_handle h, default: break; } - if (restconf_reply_send(req, 200, cbret) < 0) - goto done; + if (restconf_reply_send(req, 200, cbret, 0) < 0) + goto done; + cbret = NULL; ok: retval = 0; done: diff --git a/apps/restconf/restconf_native.c b/apps/restconf/restconf_native.c new file mode 100644 index 00000000..ebc08189 --- /dev/null +++ b/apps/restconf/restconf_native.c @@ -0,0 +1,221 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand + Copyright (C) 2020-2021 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 ***** + + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include + +/* restconf */ +#include "restconf_lib.h" /* generic shared with plugins */ +#ifdef HAVE_LIBEVHTP +#include /* evbuffer */ +#define EVHTP_DISABLE_REGEX +#define EVHTP_DISABLE_EVTHR + +#include + +#endif +#ifdef HAVE_LIBNGHTTP2 +#include +#endif +#include "restconf_native.h" /* Restconf-openssl mode specific headers*/ + +restconf_stream_data * +restconf_stream_data_new(restconf_conn *rc, + int32_t stream_id) +{ + restconf_stream_data *sd; + + if ((sd = malloc(sizeof(restconf_stream_data))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + return NULL; + } + memset(sd, 0, sizeof(restconf_stream_data)); + sd->sd_stream_id = stream_id; + sd->sd_fd = -1; + if ((sd->sd_indata = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + return NULL; + } + if ((sd->sd_outp_hdrs = cvec_new(0)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_new"); + return NULL; + } + if ((sd->sd_outp_buf = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + return NULL; + } + sd->sd_conn = rc; + INSQ(sd, rc->rc_streams); + return sd; +} + +restconf_stream_data * +restconf_stream_find(restconf_conn *rc, + int32_t id) +{ + restconf_stream_data *sd; + + if ((sd = rc->rc_streams) != NULL) { + do { + if (sd->sd_stream_id == id) + return sd; + sd = NEXTQ(restconf_stream_data *, sd); + } while (sd && sd != rc->rc_streams); + } + return NULL; +} + +int +restconf_stream_free(restconf_stream_data *sd) +{ + if (sd->sd_fd != -1) { + close(sd->sd_fd); + } + if (sd->sd_indata) + cbuf_free(sd->sd_indata); + if (sd->sd_outp_hdrs) + cvec_free(sd->sd_outp_hdrs); + if (sd->sd_outp_buf) + cbuf_free(sd->sd_outp_buf); + if (sd->sd_body) + cbuf_free(sd->sd_body); + if (sd->sd_path) + free(sd->sd_path); + free(sd); + return 0; +} + +/*! Create restconf connection struct + */ +restconf_conn * +restconf_conn_new(clicon_handle h, + int s) +{ + restconf_conn *rc; + + if ((rc = (restconf_conn*)malloc(sizeof(restconf_conn))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + return NULL; + } + memset(rc, 0, sizeof(restconf_conn)); + rc->rc_h = h; + rc->rc_s = s; + return rc; +} + +/*! Free clixon/cbuf resources related to an evhtp connection + * @param[in] rc restconf connection + */ +int +restconf_conn_free(restconf_conn *rc) +{ + restconf_stream_data *sd; + + if (rc == NULL){ + clicon_err(OE_RESTCONF, EINVAL, "rc is NULL"); + return -1; + } + /* Free all streams */ + while ((sd = rc->rc_streams) != NULL) { + DELQ(sd, rc->rc_streams, restconf_stream_data *); + if (sd) + restconf_stream_free(sd); + } + free(rc); + return 0; +} + +/*! Given SSL connection, get peer certificate one-line name + * @param[in] ssl SSL session + * @param[out] oneline Cert name one-line + */ +int +ssl_x509_name_oneline(SSL *ssl, + char **oneline) +{ + int retval = -1; + char *p = NULL; + X509 *cert = NULL; + X509_NAME *name; + + if (ssl == NULL || oneline == NULL) { + clicon_err(OE_RESTCONF, EINVAL, "ssl or cn is NULL"); + goto done; + } + if ((cert = SSL_get_peer_certificate(ssl)) == NULL) + goto ok; + if ((name = X509_get_subject_name(cert)) == NULL) + goto ok; + if ((p = X509_NAME_oneline(name, NULL, 0)) == NULL) + goto ok; + if ((*oneline = strdup(p)) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + ok: + retval = 0; + done: + if (p) + OPENSSL_free(p); + if (cert) + X509_free(cert); + return retval; +} diff --git a/apps/restconf/restconf_native.h b/apps/restconf/restconf_native.h index 86cfa026..3b152dc5 100644 --- a/apps/restconf/restconf_native.h +++ b/apps/restconf/restconf_native.h @@ -46,7 +46,6 @@ * | rr restconf_request| per-packet * +--------------------+ * - * Parse functions */ #ifdef __cplusplus @@ -59,40 +58,50 @@ extern "C" { /* * Types */ -/* http/2 session stream struct + +/* Forward */ +struct restconf_conn; + +/* session stream struct, mainly for http/2 but htp/1 has a single pseudo-stream with id=0 */ typedef struct { - qelem_t sd_qelem; /* List header */ - int32_t sd_stream_id; - int sd_fd; + qelem_t sd_qelem; /* List header */ + int32_t sd_stream_id; + int sd_fd; /* XXX Is this used? */ + cvec *sd_outp_hdrs; /* List of output headers */ + cbuf *sd_outp_buf; /* Output buffer */ + cbuf *sd_body; /* http output body as cbuf terminated with \r\n */ + size_t sd_body_offset; /* Offset into body */ + cbuf *sd_indata; /* Receive/input data */ + char *sd_path; /* Uri path, uri-encoded, without args (eg ?) */ + uint16_t sd_code; /* If != 0 send a reply XXX: need reply flag? */ + struct restconf_conn *sd_conn; /* Backpointer to connection this stream is part of */ + restconf_http_proto sd_proto; /* http protocol XXX not sure this is needed */ + void *sd_req; /* Lib-specific request, eg evhtp_request_t * */ } restconf_stream_data; -/* Restconf connection handle + /* Restconf connection handle * Per connection request */ -typedef struct { +typedef struct restconf_conn { // qelem_t rs_qelem; /* List header */ - cvec *rc_outp_hdrs; /* List of output headers */ - cbuf *rc_outp_buf; /* Output buffer */ size_t rc_bufferevent_output_offset; /* Kludge to drain libevent output buffer */ restconf_http_proto rc_proto; /* HTTP protocol: http/1 or http/2 */ int rc_s; /* Connection socket */ clicon_handle rc_h; /* Clixon handle */ SSL *rc_ssl; /* Structure for SSL connection */ - restconf_stream_data *rc_streams; /* List of http/2 session streams */ + restconf_stream_data *rc_streams; /* List of http/2 session streams */ /* Decision to keep lib-specific data here, otherwise new struct necessary * drawback is specific includes need to go everywhere */ #ifdef HAVE_LIBEVHTP evhtp_connection_t *rc_evconn; #endif #ifdef HAVE_LIBNGHTTP2 - - nghttp2_session *rc_ngsession; + nghttp2_session *rc_ngsession; /* XXX Not sure it is needed */ #endif -} restconf_conn_h; - -/* Restconf request handle - * Per socket request +} restconf_conn; + +/* Restconf per socket handle */ typedef struct { qelem_t rs_qelem; /* List header */ @@ -117,8 +126,13 @@ typedef struct { /* * Prototypes */ -int restconf_parse(void *req, const char *buf, size_t buflen); - +restconf_stream_data *restconf_stream_data_new(restconf_conn *rc, int32_t stream_id); +restconf_stream_data *restconf_stream_find(restconf_conn *rc, int32_t id); +int restconf_stream_free(restconf_stream_data *sd); +restconf_conn *restconf_conn_new(clicon_handle h, int s); +int restconf_conn_free(restconf_conn *rc); +int ssl_x509_name_oneline(SSL *ssl, char **oneline); + #endif /* _RESTCONF_NATIVE_H_ */ #ifdef __cplusplus diff --git a/apps/restconf/restconf_nghttp2.c b/apps/restconf/restconf_nghttp2.c index 1ac0a759..f770aa68 100644 --- a/apps/restconf/restconf_nghttp2.c +++ b/apps/restconf/restconf_nghttp2.c @@ -32,6 +32,18 @@ ***** END LICENSE BLOCK ***** + * nghttp2 callback mechanism + * + * nghttp2_session_mem_recv() + * on_begin_headers_callback() + * create sd + * on_header_callback() NGHTTP2_HEADERS + * translate all headers + * on_data_chunk_recv_callback + * get indata + * on_frame_recv_callback NGHTTP2_FLAG_END_STREAM + * get method and call handler + * create rr */ #ifdef HAVE_CONFIG_H @@ -93,53 +105,23 @@ #define ARRLEN(x) (sizeof(x) / sizeof(x[0])) -static restconf_stream_data * -restconf_stream_data_new(restconf_conn_h *rc, - int32_t stream_id) -{ - restconf_stream_data *sd; - - sd = malloc(sizeof(restconf_stream_data)); - memset(sd, 0, sizeof(restconf_stream_data)); - sd->sd_stream_id = stream_id; - sd->sd_fd = -1; - INSQ(sd, rc->rc_streams); - return sd; - } - -#ifdef NOTUSED -static void -delete_http2_stream_data(restconf_stream_data *sd) -{ - if (sd->fd != -1) { - close(sd->fd); - } - free(sd->request_path); - free(sd); -} -#endif - -#ifdef NOTUSED -static int -send_client_connection_header(nghttp2_session *session) -{ - int retval = -1; - nghttp2_settings_entry iv[1] = { - {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; - int rv; - - clicon_debug(1, "%s", __FUNCTION__); - /* client 24 bytes magic string will be sent by nghttp2 library */ - rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, ARRLEN(iv)); - if (rv != 0) { - clicon_err(OE_XML, 0, "Could not submit SETTINGS: %s", nghttp2_strerror(rv)); - goto done; - } - retval = 0; - done: - return retval; -} -#endif /* NOTUSED */ +/*! Map http2 frame types in nghttp2 + * I had expected it in in libnghttp2 but havent found it + */ +static const map_str2int nghttp2_frame_type_map[] = { + {"DATA", NGHTTP2_DATA}, + {"HEADERS", NGHTTP2_HEADERS}, + {"PRIORITY", NGHTTP2_PRIORITY}, + {"RST_STREAM", NGHTTP2_RST_STREAM}, + {"SETTINGS", NGHTTP2_SETTINGS}, + {"PUSH_PROMISE", NGHTTP2_PUSH_PROMISE}, + {"PING", NGHTTP2_PING}, + {"GOAWAY", NGHTTP2_GOAWAY}, + {"WINDOW_UPDATE", NGHTTP2_WINDOW_UPDATE}, + {"CONTINUATION", NGHTTP2_CONTINUATION}, + {"ALTSVC", NGHTTP2_ALTSVC}, + {NULL, -1} +}; /* Clixon error category specialized log callback for nghttp2 * @param[in] handle Application-specific handle @@ -156,7 +138,6 @@ clixon_nghttp2_log_cb(void *handle, return 0; } - #ifdef NOTUSED static void nghttp2_print_header(const uint8_t *name, @@ -181,35 +162,94 @@ nghttp2_print_headers(nghttp2_nv *nva, } #endif /* NOTUSED */ -/*! Transmit the |data|, |length| bytes, to the network. +/*! Send data to remote peer, Send at most the |length| bytes of |data|. * This callback is required if the application uses * `nghttp2_session_send()` to send data to the remote endpoint. If * the application uses solely `nghttp2_session_mem_send()` instead, * this callback function is unnecessary. + * XXX see buf_write */ static ssize_t -send_callback(nghttp2_session *session, - const uint8_t *data, - size_t length, - int flags, - void *user_data) +session_send_callback(nghttp2_session *session, + const uint8_t *buf, + size_t buflen, + int flags, + void *user_data) { - restconf_conn_h *rc = (restconf_conn_h *)user_data; - int ret; + int retval = -1; + restconf_conn *rc = (restconf_conn *)user_data; + int er; + ssize_t len; + ssize_t totlen = 0; + int s; + SSL *ssl; - clicon_debug(1, "%s %zu:", __FUNCTION__, length); -#if 0 - { - int i; - for (i=0; irc_s; + ssl = rc->rc_ssl; + while (totlen < buflen){ + if (ssl){ + if ((len = SSL_write(ssl, buf+totlen, buflen-totlen)) <= 0){ + er = errno; + switch (SSL_get_error(ssl, len)){ + case SSL_ERROR_SYSCALL: /* 5 */ + if (er == ECONNRESET) {/* Connection reset by peer */ + if (ssl) + SSL_free(ssl); + close(s); + // XXX clixon_event_unreg_fd(s, restconf_connection); + goto ok; /* Close socket and ssl */ + } + else if (er == EAGAIN){ + clicon_debug(1, "%s write EAGAIN", __FUNCTION__); + usleep(10000); + continue; + } + else{ + clicon_err(OE_RESTCONF, er, "SSL_write %d", er); + goto done; + } + break; + default: + clicon_err(OE_SSL, 0, "SSL_write"); + goto done; + break; + } + goto done; + } + } + else{ + if ((len = write(s, buf+totlen, buflen-totlen)) < 0){ + if (errno == EAGAIN){ + clicon_debug(1, "%s write EAGAIN", __FUNCTION__); + usleep(10000); + continue; + } +#if 1 + else if (errno == ECONNRESET) {/* Connection reset by peer */ + close(s); + // XXX clixon_event_unreg_fd(s, restconf_connection); + goto ok; /* Close socket and ssl */ + } #endif - /* encrypt & send message */ - if ((ret = SSL_write(rc->rc_ssl, data, length)) < 0) - return ret; - return ret; + else{ + clicon_err(OE_UNIX, errno, "write"); + goto done; + } + } + assert(len != 0); + } + totlen += len; + } /* while */ + ok: + retval = 0; + done: + if (retval < 0){ + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + return retval; + } + clicon_debug(1, "%s retval:%lu", __FUNCTION__, totlen); + return totlen; } /*! Invoked when |session| wants to receive data from the remote peer. @@ -221,7 +261,7 @@ recv_callback(nghttp2_session *session, int flags, void *user_data) { - // restconf_conn_h *rc = (restconf_conn_h *)user_data; + // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); return 0; } @@ -239,31 +279,57 @@ recv_callback(nghttp2_session *session, * 2) terminating the process? */ static int -restconf_nghttp2_root(restconf_conn_h *rc) +restconf_nghttp2_path(restconf_stream_data *sd) { - int retval = -1; - clicon_handle h; - // int ret; - cvec *qvec = NULL; + int retval = -1; + clicon_handle h; + cvec *qvec = NULL; + char *query = NULL; + restconf_conn *rc; + char *oneline = NULL; + cvec *cvv = NULL; + char *cn; clicon_debug(1, "------------"); + rc = sd->sd_conn; if ((h = rc->rc_h) == NULL){ clicon_err(OE_RESTCONF, EINVAL, "arg is NULL"); goto done; } - /* get accepted connection */ /* Query vector, ie the ?a=x&b=y stuff */ - if ((qvec = cvec_new(0)) ==NULL){ - clicon_err(OE_UNIX, errno, "cvec_new"); - goto done; + query = restconf_param_get(h, "REQUEST_URI"); + if ((query = index(query, '?')) != NULL){ + query++; + if (strlen(query) && + uri_str2cvec(query, '&', '=', 1, &qvec) < 0) + goto done; + } + /* Slightly awkward way of taking SSL cert subject and CN and add it to restconf parameters + * instead of accessing it directly */ + if (rc->rc_ssl != NULL){ + /* SSL subject fields, eg CN (Common Name) , can add more here? */ + if (ssl_x509_name_oneline(rc->rc_ssl, &oneline) < 0) + goto done; + if (oneline != NULL) { + if (uri_str2cvec(oneline, '/', '=', 1, &cvv) < 0) + goto done; + if ((cn = cvec_find_str(cvv, "CN")) != NULL){ + if (restconf_param_set(h, "SSL_CN", cn) < 0) + goto done; + } + } } /* call generic function */ - if (api_root_restconf(h, rc, qvec) < 0) + if (strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0){ + if (api_well_known(h, sd) < 0) + goto done; + } + else if (api_root_restconf(h, sd, qvec) < 0) goto done; - // /* Clear (fcgi) paramaters from this request */ - // if (restconf_param_del_all(h) < 0) - // goto done; + /* Clear (fcgi) paramaters from this request */ + if (restconf_param_del_all(h) < 0) + goto done; retval = 0; done: clicon_debug(1, "%s %d", __FUNCTION__, retval); @@ -271,12 +337,123 @@ restconf_nghttp2_root(restconf_conn_h *rc) // if (retval < 0){ // evhtp_send_reply(req, EVHTP_RES_ERROR); // } + if (cvv) + cvec_free(cvv); + if (oneline) + free(oneline); if (qvec) cvec_free(qvec); return retval; /* void */ } -/*! +/*! data callback, just pass pointer to cbuf + * XXX handle several chunks with cbuf + */ +static ssize_t +restconf_sd_read(nghttp2_session *session, + int32_t stream_id, + uint8_t *buf, + size_t length, + uint32_t *data_flags, + nghttp2_data_source *source, + void *user_data) +{ + restconf_stream_data *sd = (restconf_stream_data *)source->ptr; + cbuf *cb; + size_t len = 0; + size_t remain; + + if ((cb = sd->sd_body) == NULL){ /* shouldnt happen */ + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + return 0; + } +#if 0 + if (cbuf_len(cb) <= length){ + len = remain; + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + } + else{ + len = length; + } + memcpy(buf, cbuf_get(cb) + sd->sd_body_offset, len); + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + return len; +#endif + assert(cbuf_len(cb) > sd->sd_body_offset); + remain = cbuf_len(cb) - sd->sd_body_offset; + clicon_debug(1, "%s length:%lu totlen:%d, offset:%lu remain:%lu", + __FUNCTION__, + length, + cbuf_len(cb), + sd->sd_body_offset, + remain); + + if (remain <= length){ + len = remain; + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + } + else{ + len = length; + } + memcpy(buf, cbuf_get(cb) + sd->sd_body_offset, len); + sd->sd_body_offset += len; + clicon_debug(1, "%s retval:%lu", __FUNCTION__, len); + return len; +} + +static int +restconf_submit_response(nghttp2_session *session, + restconf_conn *rc, + int stream_id, + restconf_stream_data *sd) +{ + int retval = -1; + nghttp2_data_provider data_prd; + nghttp2_error ngerr; + cg_var *cv; + nghttp2_nv *hdrs; + nghttp2_nv *hdr; + int i = 0; + char valstr[16]; + + clicon_debug(1, "%s", __FUNCTION__); + data_prd.source.ptr = sd; + data_prd.read_callback = restconf_sd_read; + if ((hdrs = (nghttp2_nv*)calloc(1+cvec_len(sd->sd_outp_hdrs), sizeof(nghttp2_nv))) == NULL){ + clicon_err(OE_UNIX, errno, "calloc"); + goto done; + } + hdr = &hdrs[i++]; + hdr->name = (uint8_t*)":status"; + snprintf(valstr, 15, "%u", sd->sd_code); + hdr->value = (uint8_t*)valstr; + hdr->namelen = strlen(":status"); + hdr->valuelen = strlen(valstr); + clicon_debug(1, "%s val:'%s' valuelen:%lu", __FUNCTION__, hdr->value, hdr->valuelen); + hdr->flags = 0; + + cv = NULL; + while ((cv = cvec_each(sd->sd_outp_hdrs, cv)) != NULL){ + hdr = &hdrs[i++]; + hdr->name = (uint8_t*)cv_name_get(cv); + hdr->value = (uint8_t*)cv_string_get(cv); + hdr->namelen = strlen(cv_name_get(cv)); + hdr->valuelen = strlen(cv_string_get(cv)); + hdr->flags = 0; + } + if ((ngerr = nghttp2_submit_response(session, + stream_id, + hdrs, i, + (data_prd.source.ptr != NULL)?&data_prd:NULL)) < 0){ + clicon_err(OE_NGHTTP2, ngerr, "nghttp2_submit_response"); + goto done; + } + retval = 0; + done: + return retval; +} + +/*! A frame is received */ static int on_frame_recv_callback(nghttp2_session *session, @@ -284,30 +461,39 @@ on_frame_recv_callback(nghttp2_session *session, void *user_data) { int retval = -1; - restconf_conn_h *rc = (restconf_conn_h *)user_data; - restconf_stream_data *sd; - char *path; + restconf_conn *rc = (restconf_conn *)user_data; + restconf_stream_data *sd = NULL; - clicon_debug(1, "%s %d", __FUNCTION__, frame->hd.stream_id); + clicon_debug(1, "%s %s %d", __FUNCTION__, + clicon_int2str(nghttp2_frame_type_map, frame->hd.type), + frame->hd.stream_id); switch (frame->hd.type) { case NGHTTP2_DATA: case NGHTTP2_HEADERS: /* Check that the client request has finished */ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { /* For DATA and HEADERS frame, this callback may be called after - on_stream_close_callback. Check that stream still alive. */ + on_stream_close_callback. Check that stream still alive. + */ if ((sd = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)) == NULL) return 0; - if ((path = restconf_uripath(rc->rc_h)) == NULL) + if ((sd->sd_path = restconf_uripath(rc->rc_h)) == NULL) goto ok; - if (strcmp(path, "/" RESTCONF_API) == 0){ - if (restconf_nghttp2_root(rc) < 0) + sd->sd_proto = HTTP_2; /* XXX is this necessary? */ + if (strncmp(sd->sd_path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0 || + strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0){ + if (restconf_nghttp2_path(sd) < 0) goto done; } - else if (strcmp(path, RESTCONF_WELL_KNOWN) == 0){ - } else ; /* ignore */ + if (sd->sd_code){ + if (restconf_submit_response(session, rc, frame->hd.stream_id, sd) < 0) + goto done; + } + else { + /* 500 Internal server error ? */ + } } break; default: @@ -319,7 +505,7 @@ on_frame_recv_callback(nghttp2_session *session, return retval; } -/*! +/*! An invalid non-DATA frame is received. */ static int on_invalid_frame_recv_callback(nghttp2_session *session, @@ -327,12 +513,12 @@ on_invalid_frame_recv_callback(nghttp2_session *session, int lib_error_code, void *user_data) { - // restconf_conn_h *rc = (restconf_conn_h *)user_data; + // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); return 0; } -/*! +/*! A chunk of data in DATA frame is received */ static int on_data_chunk_recv_callback(nghttp2_session *session, @@ -342,41 +528,41 @@ on_data_chunk_recv_callback(nghttp2_session *session, size_t len, void *user_data) { - // restconf_conn_h *rc = (restconf_conn_h *)user_data; + restconf_conn *rc = (restconf_conn *)user_data; + restconf_stream_data *sd; clicon_debug(1, "%s %d", __FUNCTION__, stream_id); - // if (sd->sd_session == session && - // sd->sd_stream_id == stream_id) - // fwrite(data, 1, len, stdout); /* This is where data is printed */ + if ((sd = restconf_stream_find(rc, stream_id)) != NULL){ + cbuf_append_buf(sd->sd_indata, (void*)data, len); + } return 0; } -/*! +/*! Just before the non-DATA frame |frame| is sent */ static int before_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { - // restconf_conn_h *rc = (restconf_conn_h *)user_data; - + // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); return 0; } -/*! +/*! After the frame |frame| is sent */ static int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { - // restconf_conn_h *rc = (restconf_conn_h *)user_data; + // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); return 0; } -/*! +/*! After the non-DATA frame |frame| is not sent because of error */ static int on_frame_not_send_callback(nghttp2_session *session, @@ -384,12 +570,12 @@ on_frame_not_send_callback(nghttp2_session *session, int lib_error_code, void *user_data) { - // restconf_conn_h *rc = (restconf_conn_h *)user_data; + // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); return 0; } -/*! +/*! Stream |stream_id| is closed. */ static int on_stream_close_callback(nghttp2_session *session, @@ -397,7 +583,7 @@ on_stream_close_callback(nghttp2_session *session, nghttp2_error_code error_code, void *user_data) { - // restconf_conn_h *rc = (restconf_conn_h *)user_data; + // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); //session_data *sd = (session_data*)user_data; return 0; @@ -410,10 +596,10 @@ on_begin_headers_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { - restconf_conn_h *rc = (restconf_conn_h *)user_data; + restconf_conn *rc = (restconf_conn *)user_data; restconf_stream_data *sd; - clicon_debug(1, "%s", __FUNCTION__); + clicon_debug(1, "%s %s", __FUNCTION__, clicon_int2str(nghttp2_frame_type_map, frame->hd.type)); if (frame->hd.type == NGHTTP2_HEADERS && frame->headers.cat == NGHTTP2_HCAT_REQUEST) { sd = restconf_stream_data_new(rc, frame->hd.stream_id); @@ -422,45 +608,6 @@ on_begin_headers_callback(nghttp2_session *session, return 0; } -#ifdef XXX -/*! Translate http header by capitalizing, prepend w HTTP_ and - -> _ - * Example: Host -> HTTP_HOST - */ -static int -evhtp_convert_fcgi(evhtp_header_t *hdr, - void *arg) -{ - int retval = -1; - clicon_handle h = (clicon_handle)arg; - cbuf *cb = NULL; - int i; - char c; - - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - /* convert key name */ - cprintf(cb, "HTTP_"); - for (i=0; ikey); i++){ - c = hdr->key[i] & 0xff; - if (islower(c)) - cprintf(cb, "%c", toupper(c)); - else if (c == '-') - cprintf(cb, "_"); - else - cprintf(cb, "%c", c); - } - if (restconf_param_set(h, cbuf_get(cb), hdr->val) < 0) - goto done; - retval = 0; - done: - if (cb) - cbuf_free(cb); - return retval; -} -#endif - /*! Map from nghttp2 headers to "fcgi" type parameters used in clixon code * Both |name| and |value| are guaranteed to be NULL-terminated. */ @@ -472,8 +619,8 @@ nghttp2_hdr2clixon(clicon_handle h, int retval = -1; if (strcmp(name, ":path") == 0){ - /* XXX "/restconf" Is PATH really REQUEST_URI? */ - if (restconf_param_set(h, "REQUEST_URI", value) < 0) /* XXX string? */ + /* Including ?args, call restconf_uripath() to get only path */ + if (restconf_param_set(h, "REQUEST_URI", value) < 0) goto done; } else if (strcmp(name, ":method") == 0){ @@ -513,20 +660,17 @@ on_header_callback(nghttp2_session *session, void *user_data) { int retval = -1; - restconf_conn_h *rc = (restconf_conn_h *)user_data; - restconf_stream_data *sd; + restconf_conn *rc = (restconf_conn *)user_data; - clicon_debug(1, "%s %d:", __FUNCTION__, frame->hd.stream_id); switch (frame->hd.type){ case NGHTTP2_HEADERS: assert (frame->headers.cat == NGHTTP2_HCAT_REQUEST); - clicon_debug(1, "%s %s %s", __FUNCTION__, name, value); - if ((sd = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)) == NULL) - break; + clicon_debug(1, "%s HEADERS %s %s", __FUNCTION__, name, value); if (nghttp2_hdr2clixon(rc->rc_h, (char*)name, (char*)value) < 0) goto done; break; default: + clicon_debug(1, "%s %s %s", __FUNCTION__, clicon_int2str(nghttp2_frame_type_map, frame->hd.type), name); break; } retval = 0; @@ -534,7 +678,8 @@ on_header_callback(nghttp2_session *session, return retval; } -/*! +#ifdef NOTUSED +/*! How many padding bytes are required for the transmission of the |frame|? */ static ssize_t select_padding_callback(nghttp2_session *session, @@ -542,12 +687,12 @@ select_padding_callback(nghttp2_session *session, size_t max_payloadlen, void *user_data) { - // restconf_conn_h *rc = (restconf_conn_h *)user_data; + // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); - return 0; + return frame->hd.length; } -/*! +/*! Get max length of data to send data to the remote peer */ static ssize_t data_source_read_length_callback(nghttp2_session *session, @@ -558,10 +703,11 @@ data_source_read_length_callback(nghttp2_session *session, uint32_t remote_max_frame_size, void *user_data) { - // restconf_conn_h *rc = (restconf_conn_h *)user_data; + // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); return 0; } +#endif /* NOTUSED */ /*! Invoked when a frame header is received. * Unlike :type:`nghttp2_on_frame_recv_callback`, this callback will @@ -572,15 +718,17 @@ on_begin_frame_callback(nghttp2_session *session, const nghttp2_frame_hd *hd, void *user_data) { - // restconf_conn_h *rc = (restconf_conn_h *)user_data; - clicon_debug(1, "%s type:%d", __FUNCTION__, hd->type); - + // restconf_conn *rc = (restconf_conn *)user_data; + clicon_debug(1, "%s %s", __FUNCTION__, clicon_int2str(nghttp2_frame_type_map, hd->type)); if (hd->type == NGHTTP2_CONTINUATION) assert(0); return 0; } -/*! +/*! Send complete DATA frame for no-copy + * Callback function invoked when :enum:`NGHTTP2_DATA_FLAG_NO_COPY` is + * used in :type:`nghttp2_data_source_read_callback` to send complete + * DATA frame. */ static int send_data_callback(nghttp2_session *session, @@ -589,12 +737,13 @@ send_data_callback(nghttp2_session *session, nghttp2_data_source *source, void *user_data) { - // restconf_conn_h *rc = (restconf_conn_h *)user_data; + // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); return 0; } -/*! +#ifdef NOTUSED +/*! Pack extension payload in its wire format */ static ssize_t pack_extension_callback(nghttp2_session *session, @@ -602,12 +751,12 @@ pack_extension_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { - // restconf_conn_h *rc = (restconf_conn_h *)user_data; + // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); return 0; } -/*! +/*! Unpack extension payload from its wire format. */ static int unpack_extension_callback(nghttp2_session *session, @@ -615,12 +764,13 @@ unpack_extension_callback(nghttp2_session *session, const nghttp2_frame_hd *hd, void *user_data) { - // restconf_conn_h *rc = (restconf_conn_h *)user_data; + // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); return 0; } +#endif /* NOTUSED */ -/*! +/*! Chunk of extension frame payload is received */ static int on_extension_chunk_recv_callback(nghttp2_session *session, @@ -629,25 +779,12 @@ on_extension_chunk_recv_callback(nghttp2_session *session, size_t len, void *user_data) { - // restconf_conn_h *rc = (restconf_conn_h *)user_data; + // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); return 0; } -/*! - */ -static int -error_callback(nghttp2_session *session, - const char *msg, - size_t len, - void *user_data) -{ - // restconf_conn_h *rc = (restconf_conn_h *)user_data; - clicon_debug(1, "%s", __FUNCTION__); - return 0; -} - -/*! +/*! Library provides the error code, and message for debugging purpose. */ static int error_callback2(nghttp2_session *session, @@ -656,8 +793,9 @@ error_callback2(nghttp2_session *session, size_t len, void *user_data) { - // restconf_conn_h *rc = (restconf_conn_h *)user_data; + // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); + clicon_err(OE_NGHTTP2, lib_error_code, "%s", msg); return 0; } @@ -665,22 +803,57 @@ error_callback2(nghttp2_session *session, * XXX see session_recv */ int -http2_recv(restconf_conn_h *rc, +http2_recv(restconf_conn *rc, const unsigned char *buf, size_t n) { - int retval = -1; - nghttp2_error ngerr; + int retval = -1; + nghttp2_error ngerr; clicon_debug(1, "%s", __FUNCTION__); if (rc->rc_ngsession == NULL){ - clicon_err(OE_RESTCONF, EINVAL, "No nghttp2 session"); + /* http2_session_init not called */ + clicon_err(OE_RESTCONF, EINVAL, "No nghttp2 session"); goto done; } + /* may make additional pending frames */ if ((ngerr = nghttp2_session_mem_recv(rc->rc_ngsession, buf, n)) < 0){ clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_mem_recv"); goto done; } + /* sends highest prio frame from outbound queue to remote peer. It does this as + * many as possible until user callback :type:`nghttp2_send_callback` returns + * * :enum:`NGHTTP2_ERR_WOULDBLOCK` or the outbound queue becomes empty. + */ + if ((ngerr = nghttp2_session_send(rc->rc_ngsession)) != 0){ + clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_send"); + goto done; + } + retval = 0; + done: + return retval; +} + +/* Send HTTP/2 client connection header, which includes 24 bytes + magic octets and SETTINGS frame */ +int +http2_send_server_connection(restconf_conn *rc) +{ + int retval = -1; + nghttp2_settings_entry iv[1] = {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; + nghttp2_error ngerr; + + if ((ngerr = nghttp2_submit_settings(rc->rc_ngsession, + NGHTTP2_FLAG_NONE, + iv, + ARRLEN(iv))) != 0){ + clicon_err(OE_NGHTTP2, ngerr, "nghttp2_submit_settings"); + goto done; + } + if ((ngerr = nghttp2_session_send(rc->rc_ngsession)) != 0){ + clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_send"); + goto done; + } retval = 0; done: return retval; @@ -689,13 +862,15 @@ http2_recv(restconf_conn_h *rc, /*! Initialize callbacks */ int -http2_session_init(restconf_conn_h *rc) +http2_session_init(restconf_conn *rc) { + int retval = -1; nghttp2_session_callbacks *callbacks = NULL; nghttp2_session *session = NULL; + nghttp2_error ngerr; nghttp2_session_callbacks_new(&callbacks); - nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); + nghttp2_session_callbacks_set_send_callback(callbacks, session_send_callback); nghttp2_session_callbacks_set_recv_callback(callbacks, recv_callback); nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, on_frame_recv_callback); nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(callbacks, on_invalid_frame_recv_callback); @@ -706,22 +881,31 @@ http2_session_init(restconf_conn_h *rc) nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, on_stream_close_callback); nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, on_begin_headers_callback); nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header_callback); +#ifdef NOTUSED nghttp2_session_callbacks_set_select_padding_callback(callbacks, select_padding_callback); nghttp2_session_callbacks_set_data_source_read_length_callback(callbacks, data_source_read_length_callback); +#endif nghttp2_session_callbacks_set_on_begin_frame_callback(callbacks, on_begin_frame_callback); nghttp2_session_callbacks_set_send_data_callback(callbacks, send_data_callback); +#ifdef NOTUSED nghttp2_session_callbacks_set_pack_extension_callback(callbacks, pack_extension_callback); nghttp2_session_callbacks_set_unpack_extension_callback(callbacks, unpack_extension_callback); +#endif nghttp2_session_callbacks_set_on_extension_chunk_recv_callback(callbacks, on_extension_chunk_recv_callback); - nghttp2_session_callbacks_set_error_callback(callbacks, error_callback); nghttp2_session_callbacks_set_error_callback2(callbacks, error_callback2); - /* Register callbacks with nghttp2 */ - nghttp2_session_server_new(&session, callbacks, rc); + /* Create session for server use, register callbacks */ + if ((ngerr = nghttp2_session_server_new(&session, callbacks, rc)) < 0){ + clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_server_new"); + goto done; + } nghttp2_session_callbacks_del(callbacks); rc->rc_ngsession = session; - return 0; + + retval = 0; + done: + return retval; } #endif /* HAVE_LIBNGHTTP2 */ diff --git a/apps/restconf/restconf_nghttp2.h b/apps/restconf/restconf_nghttp2.h index 35059f7b..351dd88d 100644 --- a/apps/restconf/restconf_nghttp2.h +++ b/apps/restconf/restconf_nghttp2.h @@ -42,7 +42,8 @@ * Prototypes */ int clixon_nghttp2_log_cb(void *handle, int suberr, cbuf *cb); -int http2_recv(restconf_conn_h *rc, const unsigned char *buf, size_t n); -int http2_session_init(restconf_conn_h *rc); +int http2_recv(restconf_conn *rc, const unsigned char *buf, size_t n); +int http2_send_server_connection(restconf_conn *rc); +int http2_session_init(restconf_conn *rc); #endif /* _RESTCONF_NGHTTP2_H_ */ diff --git a/apps/restconf/restconf_root.c b/apps/restconf/restconf_root.c index 98084114..5161f8e8 100644 --- a/apps/restconf/restconf_root.c +++ b/apps/restconf/restconf_root.c @@ -85,6 +85,7 @@ api_well_known(clicon_handle h, char *request_method; cbuf *cb = NULL; int pretty; + int head; clicon_debug(1, "%s", __FUNCTION__); if (req == NULL){ @@ -92,16 +93,17 @@ api_well_known(clicon_handle h, goto done; } request_method = restconf_param_get(h, "REQUEST_METHOD"); - if (strcmp(request_method, "GET") != 0){ + head = strcmp(request_method, "HEAD") == 0; + if (!head && strcmp(request_method, "GET") != 0){ pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); - if (restconf_method_notallowed(h, req, "GET", pretty, YANG_DATA_JSON) < 0) + if (restconf_method_notallowed(h, req, "GET,HEAD", pretty, YANG_DATA_JSON) < 0) goto done; goto ok; } - if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0) - goto done; if (restconf_reply_header(req, "Content-Type", "application/xrd+xml") < 0) goto done; + if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0) + goto done; /* Create body */ if ((cb = cbuf_new()) == NULL){ clicon_err(OE_UNIX, errno, "cbuf_new"); @@ -111,8 +113,9 @@ api_well_known(clicon_handle h, cprintf(cb, " \n"); cprintf(cb, "\r\n"); - if (restconf_reply_send(req, 200, cb) < 0) + if (restconf_reply_send(req, 200, cb, head) < 0) goto done; + cb = NULL; ok: retval = 0; done: @@ -144,9 +147,11 @@ api_root_restconf_exact(clicon_handle h, yang_stmt *yspec; cxobj *xt = NULL; cbuf *cb = NULL; + int head; clicon_debug(1, "%s", __FUNCTION__); - if (strcmp(request_method, "GET") != 0){ + head = strcmp(request_method, "HEAD") == 0; + if (!head && strcmp(request_method, "GET") != 0){ if (restconf_method_notallowed(h, req, "GET", pretty, media_out) < 0) goto done; goto ok; @@ -155,11 +160,10 @@ api_root_restconf_exact(clicon_handle h, clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; } - if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0) - goto done; if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0) goto done; - + if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0) + goto done; if (clixon_xml_parse_string("" "" IETF_YANG_LIBRARY_REVISION "", @@ -184,8 +188,9 @@ api_root_restconf_exact(clicon_handle h, default: break; } - if (restconf_reply_send(req, 200, cb) < 0) + if (restconf_reply_send(req, 200, cb, head) < 0) goto done; + cb = NULL; ok: retval = 0; done: @@ -261,8 +266,9 @@ api_yang_library_version(clicon_handle h, default: break; } - if (restconf_reply_send(req, 200, cb) < 0) + if (restconf_reply_send(req, 200, cb, 0) < 0) goto done; + cb = NULL; retval = 0; done: if (cb) @@ -414,7 +420,7 @@ api_root_restconf(clicon_handle h, int retval = -1; char *request_method = NULL; /* GET,.. */ char *api_resource = NULL; /* RFC8040 3.3: eg data/operations */ - char *path; + char *path = NULL; char **pvec = NULL; cvec *pcvec = NULL; /* for rest api */ int pn; @@ -433,7 +439,8 @@ api_root_restconf(clicon_handle h, goto done; } request_method = restconf_param_get(h, "REQUEST_METHOD"); - path = restconf_uripath(h); + if ((path = restconf_uripath(h)) == NULL) + goto done; /* XXX see restconf_config_init access directly */ pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); /* Get media for output (proactive negotiation) RFC7231 by using @@ -462,6 +469,7 @@ api_root_restconf(clicon_handle h, if ((pvec = clicon_strsep(path, "/", &pn)) == NULL) goto done; + /* Sanity check of path. Should be /restconf/ */ if (pn < 2){ if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /restconf/ expected") < 0) @@ -576,7 +584,6 @@ api_root_restconf(clicon_handle h, goto done; goto ok; } - ok: retval = 0; done: @@ -589,8 +596,8 @@ api_root_restconf(clicon_handle h, cvec_free(pcvec); if (pvec) free(pvec); - if (cb) - cbuf_free(cb); + if (path) + free(path); return retval; } diff --git a/apps/restconf/restconf_stream_fcgi.c b/apps/restconf/restconf_stream_fcgi.c index a00f2b65..df165101 100644 --- a/apps/restconf/restconf_stream_fcgi.c +++ b/apps/restconf/restconf_stream_fcgi.c @@ -304,7 +304,7 @@ restconf_stream(clicon_handle h, goto done; if (restconf_reply_header(req, "X-Accel-Buffering", "no") < 0) goto done; - if (restconf_reply_send(req, 201, NULL) < 0) + if (restconf_reply_send(req, 201, NULL, 0) < 0) goto done; *sp = s; ok: @@ -378,7 +378,7 @@ api_stream(clicon_handle h, { int retval = -1; FCGX_Request *rfcgi = (FCGX_Request *)req; /* XXX */ - char *path; + char *path = NULL; char *method; char **pvec = NULL; int pn; @@ -397,7 +397,8 @@ api_stream(clicon_handle h, #endif clicon_debug(1, "%s", __FUNCTION__); - path = restconf_uripath(h); + if ((path = restconf_uripath(h)) == NULL) + goto done; /* XXX see restconf_config_init access directly */ pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); if ((pvec = clicon_strsep(path, "/", &pn)) == NULL) @@ -521,5 +522,7 @@ api_stream(clicon_handle h, cbuf_free(cb); if (cbret) cbuf_free(cbret); + if (path) + free(path); return retval; } diff --git a/configure b/configure index 437d5ea0..4f64102e 100755 --- a/configure +++ b/configure @@ -637,6 +637,7 @@ CPP wwwdir enable_optyangs with_libxml2 +with_http2 with_restconf LINKAGE SH_SUFFIX @@ -3356,6 +3357,8 @@ test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' # Set to native or fcgi -> compile apps/restconf +with_http2=false + # Home dir for web user, such as nginx fcgi sockets @@ -5360,6 +5363,7 @@ else as_fn_error $? "nghttp2 missing" "$LINENO" 5 fi + with_http2=true fi $as_echo "#define WITH_RESTCONF_NATIVE 1" >>confdefs.h diff --git a/configure.ac b/configure.ac index 7481601c..7de5fec5 100644 --- a/configure.ac +++ b/configure.ac @@ -96,6 +96,7 @@ AC_SUBST(LIBS) AC_SUBST(SH_SUFFIX) AC_SUBST(LINKAGE) AC_SUBST(with_restconf) # Set to native or fcgi -> compile apps/restconf +AC_SUBST(with_http2,false) AC_SUBST(with_libxml2) AC_SUBST(enable_optyangs) # Home dir for web user, such as nginx fcgi sockets @@ -253,6 +254,7 @@ elif test "x${with_restconf}" == xnative; then if test "$ac_enable_nghttp2" = "yes"; then AC_CHECK_HEADERS(nghttp2/nghttp2.h,[], AC_MSG_ERROR([nghttp2 missing])) AC_CHECK_LIB(nghttp2, nghttp2_session_server_new,, AC_MSG_ERROR([nghttp2 missing])) + with_http2=true fi AC_DEFINE(WITH_RESTCONF_NATIVE, 1, [Use native restconf mode]) # For c-code that cant use strings elif test "x${with_restconf}" == xno; then diff --git a/test/config.sh.in b/test/config.sh.in index 89f92c53..1f288a3f 100755 --- a/test/config.sh.in +++ b/test/config.sh.in @@ -39,6 +39,8 @@ # --with-restconf=native Integration with embedded web server libevhtp WITH_RESTCONF=@with_restconf@ # native, fcgi or "" +WITH_HTTP2=@with_http2@ # true if nghttp2 is enabled, otherwise false + # This is for libxml2 XSD regex engine # Note this only enables the compiling of the code. In order to actually # use it you need to set Clixon config option CLICON_YANG_REGEXP to libxml2 diff --git a/test/lib.sh b/test/lib.sh index 9b0bea80..08a4a83d 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -99,6 +99,14 @@ DEFAULTHELLO=" /dev/null) # echo "hdr:\"$hdr\"" let i=0; - while [[ $hdr != *"200 OK"* ]]; do + while [[ $hdr != *"200"* ]]; do # echo "wait_restconf $i" if [ $i -ge $DEMLOOP ]; then err1 "restconf timeout $DEMWAIT seconds" @@ -480,7 +488,7 @@ function expectpart(){ positive=false; elif [ $i -gt 1 ]; then # echo "echo \"$ret\" | grep --null -o \"$exp"\" - match=$(echo "$ret" | grep --null -o "$exp") # XXX -EZo: -E cant handle {} + match=$(echo "$ret" | grep --null -i -o "$exp") #-i ignore case XXX -EZo: -E cant handle {} r=$? if $positive; then if [ $r != 0 ]; then diff --git a/test/test_api.sh b/test/test_api.sh index 3b3dd3e2..d1cde5b8 100755 --- a/test/test_api.sh +++ b/test/test_api.sh @@ -247,13 +247,13 @@ XML='235 +expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/$HVER 200" ' e1mymod:some-new-iftypetrue808080e2mymod:some-new-iftypetrueif:fddi808080e3mymod:some-new-iftypetruemymod:you808080' #e123' @@ -269,7 +269,7 @@ EOF # XXX: Since derived-from etc are NOT implemented, this test may have false positives # revisit when it is implemented. new "restconf PUT augment multi-namespace path e1 (whole path)" -expectpart "$(curl $CURLOPTS -X PUT -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1 -d "$XML")" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl $CURLOPTS -X PUT -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1 -d "$XML")" 0 "HTTP/$HVER 204" XML=$(cat <23 @@ -277,19 +277,19 @@ EOF ) new "restconf POST augment multi-namespace path e2 (middle path)" -expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=e2 -d "$XML" )" 0 "HTTP/1.1 201 Created" +expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=e2 -d "$XML" )" 0 "HTTP/$HVER 201" new "restconf GET augment multi-namespace top" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"e1","type":"example-augment:some-new-iftype","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:port":80,"example-augment:lport":8080},{"name":"e2","type":"example-augment:some-new-iftype","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:other":"ietf-interfaces:fddi","example-augment:port":80,"example-augment:lport":8080},{"name":"e3","type":"example-augment:some-new-iftype","example-augment:mandatory-leaf":"true","example-augment:me":"you","example-augment:port":80,"example-augment:lport":8080}\]}}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/$HVER 200" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"e1","type":"example-augment:some-new-iftype","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:port":80,"example-augment:lport":8080},{"name":"e2","type":"example-augment:some-new-iftype","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:other":"ietf-interfaces:fddi","example-augment:port":80,"example-augment:lport":8080},{"name":"e3","type":"example-augment:some-new-iftype","example-augment:mandatory-leaf":"true","example-augment:me":"you","example-augment:port":80,"example-augment:lport":8080}\]}}' new "restconf GET augment multi-namespace level 1" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interface":\[{"name":"e1","type":"example-augment:some-new-iftype","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:port":80,"example-augment:lport":8080}\]}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1)" 0 "HTTP/$HVER 200" '{"ietf-interfaces:interface":\[{"name":"e1","type":"example-augment:some-new-iftype","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:port":80,"example-augment:lport":8080}\]}' new "restconf GET augment multi-namespace cross" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1/example-augment:ospf)" 0 'HTTP/1.1 200 OK' '{"example-augment:ospf":{"reference-bandwidth":23}}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1/example-augment:ospf)" 0 "HTTP/$HVER 200" '{"example-augment:ospf":{"reference-bandwidth":23}}' new "restconf GET augment multi-namespace cross level 2" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1/example-augment:ospf/reference-bandwidth)" 0 'HTTP/1.1 200 OK' '{"example-augment:reference-bandwidth":23}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1/example-augment:ospf/reference-bandwidth)" 0 "HTTP/$HVER 200" '{"example-augment:reference-bandwidth":23}' if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_choice.sh b/test/test_choice.sh index 106322c4..719350a8 100755 --- a/test/test_choice.sh +++ b/test/test_choice.sh @@ -173,19 +173,19 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOa42 # Add a set of entries using restconf new "POST the XML" -expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data -d "$XML")" 0 "HTTP/1.1 201 Created" +expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data -d "$XML")" 0 "HTTP/$HVER 201" new "Check entries" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-client:table -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$XML" +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-client:table -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" "$XML" new "Run $app" expectpart "$($app)" 0 '^42$' diff --git a/test/test_copy_config.sh b/test/test_copy_config.sh index e4821280..b6232066 100755 --- a/test/test_copy_config.sh +++ b/test/test_copy_config.sh @@ -172,10 +172,10 @@ fi # restconf copy new "restconf copy-config smoketest, json" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://127.0.0.1/restconf/operations/ietf-netconf:copy-config -d '{"ietf-netconf:input": {"target": {"startup": [null]},"source": {"running": [null]}}}')" 0 'HTTP/1.1 204 No Content' +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://127.0.0.1/restconf/operations/ietf-netconf:copy-config -d '{"ietf-netconf:input": {"target": {"startup": [null]},"source": {"running": [null]}}}')" 0 "HTTP/$HVER 204" new "restconf copy-config smoketest, xml" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" $RCPROTO://127.0.0.1/restconf/operations/ietf-netconf:copy-config -d '')" 0 'HTTP/1.1 204 No Content' +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" $RCPROTO://127.0.0.1/restconf/operations/ietf-netconf:copy-config -d '')" 0 "HTTP/$HVER 204" # Here running is empty new "Check running empty" diff --git a/test/test_debug.sh b/test/test_debug.sh index 046d68e8..8992fb43 100755 --- a/test/test_debug.sh +++ b/test/test_debug.sh @@ -81,7 +81,7 @@ new "Set backend debug using netconf" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO1]]>]]>" "^]]>]]>$" new "Set backend debug using restconf" -expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/operations/clixon-lib:debug -d '{"clixon-lib:input":{"level":1}}')" 0 'HTTP/1.1 204 No Content' +expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/operations/clixon-lib:debug -d '{"clixon-lib:input":{"level":1}}')" 0 "HTTP/$HVER 204" new "Set restconf debug using netconf" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO1]]>]]>" "^]]>]]>$" @@ -90,7 +90,7 @@ new "netconf commit" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" new "Set restconf debug using restconf" -expectpart "$(curl $CURLOPTS -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/clixon-restconf:restconf/debug -d '{"clixon-restconf:debug":1}')" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl $CURLOPTS -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/clixon-restconf:restconf/debug -d '{"clixon-restconf:debug":1}')" 0 "HTTP/$HVER 204" new "Set cli debug using cli" expectpart "$($clixon_cli -1 -f $cfg -l o debug cli 1)" 0 "^$" @@ -101,9 +101,9 @@ expectpart "$($clixon_cli -1 -f $cfg -l o debug backend 1)" 0 "^$" new "Set restconf debug using cli" expectpart "$($clixon_cli -1 -f $cfg -l o debug restconf 1)" 0 "^$" -# Exercse debug code +# Exercise debug code new "get and put config using restconf" -expectpart "$(curl $CURLOPTS -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data?content=config --next $CURLOPTS -H "Content-Type: application/yang-data+json" -X POST $RCPROTO://localhost/restconf/data -d '{"example:table":{"parameter":{"name":"local0","value":"foo"}}}')" 0 "HTTP/1.1 200 OK" '' 'HTTP/1.1 201 Created' +expectpart "$(curl $CURLOPTS -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data?content=config --next $CURLOPTS -H "Content-Type: application/yang-data+json" -X POST $RCPROTO://localhost/restconf/data -d '{"example:table":{"parameter":{"name":"local0","value":"foo"}}}')" 0 "HTTP/$HVER 200" '' "HTTP/$HVER 201" if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_identity.sh b/test/test_identity.sh index c10fd85c..1c23be90 100755 --- a/test/test_identity.sh +++ b/test/test_identity.sh @@ -274,37 +274,37 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^aes" new "restconf delete identity" -expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 204" # 2. set identity in other module with restconf , read it with restconf and netconf if ! $YANG_UNKNOWN_ANYDATA ; then new "restconf add POST instead of PUT (should fail)" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:crypto -d '{"example:crypto":"example-des:des3"}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: crypto with parent: crypto in namespace: urn:example:my-crypto"}}}' +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:crypto -d '{"example:crypto":"example-des:des3"}')" 0 "HTTP/$HVER 400" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: crypto with parent: crypto in namespace: urn:example:my-crypto"}}}' fi # Alternative error: #'{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Leaf contains sub-element"}}}' new "restconf add other (des) identity using POST" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"example:crypto":"example-des:des3"}')" 0 'HTTP/1.1 201 Created' "Location: $RCPROTO://localhost/restconf/data/example:crypto" +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"example:crypto":"example-des:des3"}')" 0 "HTTP/$HVER 201" "Location: $RCPROTO://localhost/restconf/data/example:crypto" new "restconf get other identity" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:crypto)" 0 'HTTP/1.1 200 OK' '{"example:crypto":"example-des:des3"}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 200" '{"example:crypto":"example-des:des3"}' new "netconf get other identity" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^des:des3" new "restconf delete identity" -expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 204" # 3. set identity in other module with netconf, read it with restconf and netconf new "netconf set other identity" @@ -314,7 +314,7 @@ new "netconf commit" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" new "restconf get other identity (set by netconf)" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:crypto)" 0 'HTTP/1.1 200 OK' '{"example:crypto":"example-des:des3"}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 200" '{"example:crypto":"example-des:des3"}' new "netconf get other identity" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^des:des3" diff --git a/test/test_nacm.sh b/test/test_nacm.sh index 85078da7..7b2e7460 100755 --- a/test/test_nacm.sh +++ b/test/test_nacm.sh @@ -142,11 +142,11 @@ if [ $RC -ne 0 ]; then fi new "auth get" -expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 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 -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 404" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' # explicitly disable nacm (regression on netgate bug) new "disable nacm" -expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": false}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": false}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/$HVER 201" new "auth set authentication config" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO$RULES]]>]]>" "^]]>]]>$" @@ -155,36 +155,36 @@ new "commit it" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" new "auth get (no user: access denied)" -expectpart "$(curl $CURLOPTS -X GET -H \"Accept:\ application/yang-data+json\" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 401 Unauthorized" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}} ' +expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 401" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}} ' new "auth get (wrong passwd: access denied)" -expectpart "$(curl -u andy:foo $CURLOPTS -X GET $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 401 Unauthorized" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}}' +expectpart "$(curl -u andy:foo $CURLOPTS -X GET $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 401" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}}' new "auth get (access)" -expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}' +expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 200" '{"nacm-example:x":0}' #----------------Enable NACM new "enable nacm" -expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/$HVER 204" new "admin get nacm" -expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}' +expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 200" '{"nacm-example:x":0}' new "limited get nacm" -expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}' +expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 200" '{"nacm-example:x":0}' new "guest get nacm" -expectpart "$(curl -u guest:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 403 Forbidden" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' +expectpart "$(curl -u guest:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' new "admin edit nacm" -expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x":1}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x":1}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 204" new "limited edit nacm" -expectpart "$(curl -u wilma:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 2}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 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 $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 2}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' new "guest edit nacm" -expectpart "$(curl -u guest:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 3}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 403 Forbidden" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' +expectpart "$(curl -u guest:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 3}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_nacm_datanode.sh b/test/test_nacm_datanode.sh index ca7e5a07..e3df919c 100755 --- a/test/test_nacm_datanode.sh +++ b/test/test_nacm_datanode.sh @@ -252,69 +252,69 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOlimited-acllimitedtable*read/ex:tablepermit')" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -u andy:bar $CURLOPTS -X POST $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm -H 'Content-Type: application/yang-data+xml' -d 'limited-acllimitedtable*read/ex:tablepermit')" 0 "HTTP/$HVER 201" new "Read NACM rule" -expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl)" 0 "HTTP/1.1 200 OK" '{"ietf-netconf-acm:rule-list":\[{"name":"limited-acl","group":"limited","rule":\[{"name":"table","module-name":"\*","path":"/ex:table","access-operations":"read","action":"permit"}\]}\]}' +expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl)" 0 "HTTP/$HVER 200" '{"ietf-netconf-acm:rule-list":\[{"name":"limited-acl","group":"limited","rule":\[{"name":"table","module-name":"\*","path":"/ex:table","access-operations":"read","action":"permit"}\]}\]}' new "limit read OK" -expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:table/parameters/parameter=a)" 0 'HTTP/1.1 200 OK' '{"nacm-example:parameter":\[{"name":"a","value":"72"}\]}' +expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:table/parameters/parameter=a)" 0 "HTTP/$HVER 200" '{"nacm-example:parameter":\[{"name":"a","value":"72"}\]}' new "Delete NACM read rule" -expectpart "$(curl -u andy:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -u andy:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl)" 0 "HTTP/$HVER 204" new "Fail limit read" -expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:table/parameters/parameter=a)" 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 -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:table/parameters/parameter=a)" 0 "HTTP/$HVER 404" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' new "Add NACM read path rule JSON" -expectpart "$(curl -u andy:bar $CURLOPTS -X POST $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm -H 'Content-Type: application/yang-data+json' -d '{"ietf-netconf-acm:rule-list":[{"name":"limited-acl","group":"limited","rule":[{"name":"table","module-name":"*","path":"/ex:table","access-operations":"read","action":"permit"}]}]}')" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -u andy:bar $CURLOPTS -X POST $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm -H 'Content-Type: application/yang-data+json' -d '{"ietf-netconf-acm:rule-list":[{"name":"limited-acl","group":"limited","rule":[{"name":"table","module-name":"*","path":"/ex:table","access-operations":"read","action":"permit"}]}]}')" 0 "HTTP/$HVER 201" new "Read NACM rule" -expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl)" 0 "HTTP/1.1 200 OK" '{"ietf-netconf-acm:rule-list":\[{"name":"limited-acl","group":"limited","rule":\[{"name":"table","module-name":"\*","path":"/ex:table","access-operations":"read","action":"permit"}\]}\]}' +expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl)" 0 "HTTP/$HVER 200" '{"ietf-netconf-acm:rule-list":\[{"name":"limited-acl","group":"limited","rule":\[{"name":"table","module-name":"\*","path":"/ex:table","access-operations":"read","action":"permit"}\]}\]}' new "limit read OK (Set rul w JSON)" -expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:table/parameters/parameter=a)" 0 'HTTP/1.1 200 OK' '{"nacm-example:parameter":\[{"name":"a","value":"72"}\]}' +expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:table/parameters/parameter=a)" 0 "HTTP/$HVER 200" '{"nacm-example:parameter":\[{"name":"a","value":"72"}\]}' new "Delete NACM read rule" -expectpart "$(curl -u andy:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -u andy:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl)" 0 "HTTP/$HVER 204" new "Fail limit read" -expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:table/parameters/parameter=a)" 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 -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:table/parameters/parameter=a)" 0 "HTTP/$HVER 404" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_nacm_datanode_read.sh b/test/test_nacm_datanode_read.sh index 637f3ee6..b6d534ef 100755 --- a/test/test_nacm_datanode_read.sh +++ b/test/test_nacm_datanode_read.sh @@ -179,43 +179,43 @@ function testrun(){ new "read-default:$readdefault module:$module table:$table parameter:$parameter" new "set read-default $readdefault" - expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/read-default -d "{\"ietf-netconf-acm:read-default\":\"$readdefault\"}" )" 0 "HTTP/1.1 204 No Content" + expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/read-default -d "{\"ietf-netconf-acm:read-default\":\"$readdefault\"}" )" 0 "HTTP/$HVER 204" new "set module rule $module" - expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl/rule=module/action -d "{\"ietf-netconf-acm:action\":\"$module\"}" )" 0 "HTTP/1.1 204 No Content" + expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl/rule=module/action -d "{\"ietf-netconf-acm:action\":\"$module\"}" )" 0 "HTTP/$HVER 204" new "set table rule $table" - expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl/rule=table/action -d "{\"ietf-netconf-acm:action\":\"$table\"}" )" 0 "HTTP/1.1 204 No Content" + expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl/rule=table/action -d "{\"ietf-netconf-acm:action\":\"$table\"}" )" 0 "HTTP/$HVER 204" new "set parameter rule $parameter" - expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl/rule=parameter/action -d "{\"ietf-netconf-acm:action\":\"$parameter\"}" )" 0 "HTTP/1.1 204 No Content" + expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl/rule=parameter/action -d "{\"ietf-netconf-acm:action\":\"$parameter\"}" )" 0 "HTTP/$HVER 204" #--------------- Here check new "get other module" if $test1; then - expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example2:other2/value)" 0 'HTTP/1.1 200 OK' '{"nacm-example2:value":"88"}' + expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example2:other2/value)" 0 "HTTP/$HVER 200" '{"nacm-example2:value":"88"}' else - expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example2:other2/value)" 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 -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example2:other2/value)" 0 "HTTP/$HVER 404" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' fi new "get other in same module" if $test2; then - expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:other/value)" 0 'HTTP/1.1 200 OK' '{"nacm-example:value":"99"}' + expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:other/value)" 0 "HTTP/$HVER 200" '{"nacm-example:value":"99"}' else - expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:other/value)" 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 -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:other/value)" 0 "HTTP/$HVER 404" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' fi new "get table" if $test3; then - expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:table?depth=1)" 0 'HTTP/1.1 200 OK' '{"nacm-example:table":{}}' + expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:table?depth=1)" 0 "HTTP/$HVER 200" '{"nacm-example:table":{}}' else - expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:table?depth=1)" 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 -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:table?depth=1)" 0 "HTTP/$HVER 404" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' fi new "get parameter" if $test4; then - expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:table/parameters/parameter=a)" 0 'HTTP/1.1 200 OK' '{"nacm-example:parameter":\[{"name":"a","value":"72"}\]}' + expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:table/parameters/parameter=a)" 0 "HTTP/$HVER 200" '{"nacm-example:parameter":\[{"name":"a","value":"72"}\]}' else - expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:table/parameters/parameter=a)" 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 -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:table/parameters/parameter=a)" 0 "HTTP/$HVER 404" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' fi } # testrun @@ -257,7 +257,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO$NACM$XML" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" + expectpart "$(curl -u guest:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+xml" -d "$NACM$XML" $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 201" # new "Set Initial data using POST" -# expectpart "$(curl -u guest:bar $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" -d "$XML" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" +# expectpart "$(curl -u guest:bar $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" -d "$XML" $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 201" fi @@ -136,13 +136,13 @@ EOF #----------- First get case "$ret1" in 0) ret='{"nacm-example:x":42}' - status="HTTP/1.1 200 OK" + status="HTTP/$HVER 200" ;; 1) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' - status="HTTP/1.1 403 Forbidden" + status="HTTP/$HVER 403" ;; 2) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' - status="HTTP/1.1 404 Not Found" + status="HTTP/$HVER 404" ;; esac @@ -152,10 +152,10 @@ EOF #----------- Then edit case "$ret2" in 0) ret='' - status="HTTP/1.1 204 No Content" + status="HTTP/$HVER 204" ;; 1) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' - status="HTTP/1.1 403 Forbidden" + status="HTTP/$HVER 403" ;; esac new "edit new 99" @@ -164,16 +164,16 @@ EOF #----------- Then second get case "$ret3" in 0) ret='{"nacm-example:x":99}' - status="HTTP/1.1 200 OK" + status="HTTP/$HVER 200" ;; 1) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' - status="HTTP/1.1 403 Forbidden" + status="HTTP/$HVER 403" ;; 2) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' - status="HTTP/1.1 404 Not Found" + status="HTTP/$HVER 404" ;; 3) ret='{"nacm-example:x":42}' - status="HTTP/1.1 200 OK" + status="HTTP/$HVER 200" ;; esac diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index 5b5554dc..654494ef 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -161,37 +161,37 @@ if [ $RC -ne 0 ]; then fi new "auth get" -expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data)" 0 'HTTP/1.1 200 OK' '{"data":{"clixon-example:state":{"op":\["41","42","43"\]}' +expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 200" '{"data":{"clixon-example:state":{"op":\["41","42","43"\]}' new "Set x to 0" -expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 0}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 0}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 201" new "auth get (no user: access denied)" -expectpart "$(curl $CURLOPTS -X GET -H \"Accept:\ application/yang-data+json\" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 401 Unauthorized" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}}' +expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 401" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}}' new "auth get (wrong passwd: access denied)" -expectpart "$(curl -u andy:foo $CURLOPTS -X GET $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 401 Unauthorized" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}}' +expectpart "$(curl -u andy:foo $CURLOPTS -X GET $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 401" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}}' new "auth get (access)" -expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}' +expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 200" '{"nacm-example:x":0}' new "admin get nacm" -expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}' +expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 200" '{"nacm-example:x":0}' new "limited get nacm" -expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}' +expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 200" '{"nacm-example:x":0}' new "guest get nacm" -expectpart "$(curl -u guest:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 403 Forbidden" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' +expectpart "$(curl -u guest:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' new "admin edit nacm" -expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 1}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 1}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 204" new "limited edit nacm" -expectpart "$(curl -u wilma:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 2}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 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 $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 2}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' new "guest edit nacm" -expectpart "$(curl -u guest:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 3}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 403 Forbidden" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' +expectpart "$(curl -u guest:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 3}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' new "cli show conf as admin" expectpart "$($clixon_cli -1 -U andy -l o -f $cfg show conf)" 0 "x 1;" diff --git a/test/test_nacm_module_read.sh b/test/test_nacm_module_read.sh index 8e386d06..d06a50de 100755 --- a/test/test_nacm_module_read.sh +++ b/test/test_nacm_module_read.sh @@ -151,26 +151,26 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^key42val42key43val43
]]>]]>$" new "admin read element ok" -expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:table/parameter=key42/value)" 0 'HTTP/1.1 200 OK' '{"clixon-example:value":"val42"}' +expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:table/parameter=key42/value)" 0 "HTTP/$HVER 200" '{"clixon-example:value":"val42"}' new "admin read other module OK" -expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 'HTTP/1.1 200 OK' '{"nacm-example:x":42}' +expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 200" '{"nacm-example:x":42}' new "admin read state OK" -expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 'HTTP/1.1 200 OK' '{"clixon-example:state":{"op":\["41","42","43"\]}}' +expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 "HTTP/$HVER 200" '{"clixon-example:state":{"op":\["41","42","43"\]}}' new "admin read top ok (all)" ret=$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data) @@ -183,59 +183,59 @@ fi #user:limit new "limit read ok" -expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:table)" 0 'HTTP/1.1 200 OK' '{"clixon-example:table":{"parameter":\[{"name":"key42","value":"val42"},{"name":"key43","value":"val43"}\]}}' +expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:table)" 0 "HTTP/$HVER 200" '{"clixon-example:table":{"parameter":\[{"name":"key42","value":"val42"},{"name":"key43","value":"val43"}\]}}' new "limit read netconf ok" expecteof "$clixon_netconf -U wilma -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^key42val42key43val43
]]>]]>$" new "limit read element ok" -expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:table/parameter=key42/value)" 0 'HTTP/1.1 200 OK' '{"clixon-example:value":"val42"}' +expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:table/parameter=key42/value)" 0 "HTTP/$HVER 200" '{"clixon-example:value":"val42"}' new "limit read other module fail" -expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 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 -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 404" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' new "limit read state OK" -expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 'HTTP/1.1 200 OK' '{"clixon-example:state":{"op":\["41","42","43"\]}}' +expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 "HTTP/$HVER 200" '{"clixon-example:state":{"op":\["41","42","43"\]}}' new "limit read top ok (part)" -expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data)" 0 'HTTP/1.1 200 OK' '{"data":{"clixon-example:table":{"parameter":\[{"name":"key42","value":"val42"},{"name":"key43","value":"val43"}\]},"clixon-example:state":{"op":\["41","42","43"\]}}}' +expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 200" '{"data":{"clixon-example:table":{"parameter":\[{"name":"key42","value":"val42"},{"name":"key43","value":"val43"}\]},"clixon-example:state":{"op":\["41","42","43"\]}}}' #user:guest new "guest read forbidden" -expectpart "$(curl -u guest:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:table)" 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 guest:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:table)" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' new "guest read netconf fail" expecteof "$clixon_netconf -U guest -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationaccess-deniederrordefault deny]]>]]>$" new "guest read element forbidden" -expectpart "$(curl -u guest:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:table/parameter=key42/value)" 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 guest:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:table/parameter=key42/value)" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' new "guest read other module fail" -expectpart "$(curl -u guest:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 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 guest:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' new "guest read state fail" -expectpart "$(curl -u guest:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 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 guest:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' new "guest read top ok (part)" -expectpart "$(curl -u guest:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data)" 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 guest:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' #------- RPC operation new "admin rpc ok" -expectpart "$(curl -u andy:bar $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"78"}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 200 OK' '{"clixon-example:output":{"x":"78","y":"42"}}' +expectpart "$(curl -u andy:bar $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"78"}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 "HTTP/$HVER 200" '{"clixon-example:output":{"x":"78","y":"42"}}' new "admin rpc netconf ok" expecteof "$clixon_netconf -U andy -qf $cfg" 0 "$DEFAULTHELLO0]]>]]>" "^042]]>]]>$" new "limit rpc ok" -expectpart "$(curl -u wilma:bar $CURLOPTS -X POST $RCPROTO://localhost/restconf/operations/clixon-example:example -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' )" 0 'HTTP/1.1 200 OK' '{"clixon-example:output":{"x":"42","y":"42"}}' +expectpart "$(curl -u wilma:bar $CURLOPTS -X POST $RCPROTO://localhost/restconf/operations/clixon-example:example -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' )" 0 "HTTP/$HVER 200" '{"clixon-example:output":{"x":"42","y":"42"}}' new "limit rpc netconf ok" expecteof "$clixon_netconf -U wilma -qf $cfg" 0 "$DEFAULTHELLO0]]>]]>" '^042]]>]]>$' new "guest rpc fail" -expectpart "$(curl -u guest:bar $CURLOPTS -X POST $RCPROTO://localhost/restconf/operations/clixon-example:example -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' )" 0 'HTTP/1.1 403 Forbidden' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' +expectpart "$(curl -u guest:bar $CURLOPTS -X POST $RCPROTO://localhost/restconf/operations/clixon-example:example -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' )" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' new "guest rpc netconf fail" expecteof "$clixon_netconf -U guest -qf $cfg" 0 "$DEFAULTHELLO0]]>]]>" '^applicationaccess-deniederroraccess denied]]>]]>$' @@ -243,16 +243,16 @@ expecteof "$clixon_netconf -U guest -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" new "enable nacm" - expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H 'Content-Type: application/yang-data+json' -d '{"ietf-netconf-acm:enable-nacm":true}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/1.1 204 No Content" + expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H 'Content-Type: application/yang-data+json' -d '{"ietf-netconf-acm:enable-nacm":true}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/$HVER 204" } #--------------- enable nacm @@ -184,77 +184,77 @@ nacm # delete | p/d | xp/dx | p/d # replace all, then must include NACM rules as well -# This usually triggers a 'HTTP/1.1 100 Continue' from curl as well +# This usually triggers a "HTTP/$HVER 100" from curl as well MSG="$RULES" new "update root list permit (trigger 100 Continue)" -expectpart "$(curl -u andy:bar $CURLOPTS -H 'Content-Type: application/yang-data+xml' -X PUT $RCPROTO://localhost/restconf/data -d "$MSG")" 0 'HTTP/1.1 204 No Content' +expectpart "$(curl -u andy:bar $CURLOPTS -H 'Content-Type: application/yang-data+xml' -X PUT $RCPROTO://localhost/restconf/data -d "$MSG")" 0 "HTTP/$HVER 204" new "delete root list deny" -expectpart "$(curl -u wilma:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data)" 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 $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}} ' new "delete root permit" -expectpart "$(curl -u andy:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data)" 0 'HTTP/1.1 204 No Content' +expectpart "$(curl -u andy:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 204" #--------------- re-enable nacm nacm #----------leaf new "create leaf deny" -expectpart "$(curl -u guest:bar $CURLOPTS -H 'Content-Type: application/yang-data+xml' -X PUT $RCPROTO://localhost/restconf/data/nacm-example:x -d '42')" 0 'HTTP/1.1 403 Forbidden' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}} ' +expectpart "$(curl -u guest:bar $CURLOPTS -H 'Content-Type: application/yang-data+xml' -X PUT $RCPROTO://localhost/restconf/data/nacm-example:x -d '42')" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}} ' new "create leaf permit" -expectpart "$(curl -u wilma:bar $CURLOPTS -H 'Content-Type: application/yang-data+xml' -X PUT $RCPROTO://localhost/restconf/data/nacm-example:x -d '42')" 0 'HTTP/1.1 201 Created' +expectpart "$(curl -u wilma:bar $CURLOPTS -H 'Content-Type: application/yang-data+xml' -X PUT $RCPROTO://localhost/restconf/data/nacm-example:x -d '42')" 0 "HTTP/$HVER 201" new "update leaf deny" -expectpart "$(curl -u wilma:bar $CURLOPTS -H 'Content-Type: application/yang-data+xml' -X PUT $RCPROTO://localhost/restconf/data/nacm-example:x -d '99')" 0 'HTTP/1.1 403 Forbidden' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}} ' +expectpart "$(curl -u wilma:bar $CURLOPTS -H 'Content-Type: application/yang-data+xml' -X PUT $RCPROTO://localhost/restconf/data/nacm-example:x -d '99')" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}} ' new "update leaf permit" -expectpart "$(curl -u guest:bar $CURLOPTS -H 'Content-Type: application/yang-data+xml' -X PUT $RCPROTO://localhost/restconf/data/nacm-example:x -d '99')" 0 'HTTP/1.1 204 No Content' +expectpart "$(curl -u guest:bar $CURLOPTS -H 'Content-Type: application/yang-data+xml' -X PUT $RCPROTO://localhost/restconf/data/nacm-example:x -d '99')" 0 "HTTP/$HVER 204" new "read leaf check" -expectpart "$(curl -u guest:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 'HTTP/1.1 200 OK' '{"nacm-example:x":99}' +expectpart "$(curl -u guest:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 200" '{"nacm-example:x":99}' new "delete leaf deny" -expectpart "$(curl -u guest:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 'HTTP/1.1 403 Forbidden' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}} ' +expectpart "$(curl -u guest:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}} ' new "delete leaf permit" -expectpart "$(curl -u wilma:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 'HTTP/1.1 204 No Content' +expectpart "$(curl -u wilma:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 204" #----- list/container new "create list deny" -expectpart "$(curl -u guest:bar $CURLOPTS -H 'Content-Type: application/yang-data+xml' -X PUT $RCPROTO://localhost/restconf/data/nacm-example:a=key42 -d 'key42str')" 0 'HTTP/1.1 403 Forbidden' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' +expectpart "$(curl -u guest:bar $CURLOPTS -H 'Content-Type: application/yang-data+xml' -X PUT $RCPROTO://localhost/restconf/data/nacm-example:a=key42 -d 'key42str')" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' new "create list permit" -expectpart "$(curl -u wilma:bar $CURLOPTS -H 'Content-Type: application/yang-data+xml' -X PUT $RCPROTO://localhost/restconf/data/nacm-example:a=key42 -d 'key42str')" 0 'HTTP/1.1 201 Created' +expectpart "$(curl -u wilma:bar $CURLOPTS -H 'Content-Type: application/yang-data+xml' -X PUT $RCPROTO://localhost/restconf/data/nacm-example:a=key42 -d 'key42str')" 0 "HTTP/$HVER 201" new "update list deny" -expectpart "$(curl -u wilma:bar $CURLOPTS -H 'Content-Type: application/yang-data+xml' -X PUT $RCPROTO://localhost/restconf/data/nacm-example:a=key42 -d 'key42update')" 0 'HTTP/1.1 403 Forbidden' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}' +expectpart "$(curl -u wilma:bar $CURLOPTS -H 'Content-Type: application/yang-data+xml' -X PUT $RCPROTO://localhost/restconf/data/nacm-example:a=key42 -d 'key42update')" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}' new "update list permit" -expectpart "$(curl -u guest:bar $CURLOPTS -H 'Content-Type: application/yang-data+xml' -X PUT $RCPROTO://localhost/restconf/data/nacm-example:a=key42 -d 'key42update')" 0 'HTTP/1.1 204 No Content' +expectpart "$(curl -u guest:bar $CURLOPTS -H 'Content-Type: application/yang-data+xml' -X PUT $RCPROTO://localhost/restconf/data/nacm-example:a=key42 -d 'key42update')" 0 "HTTP/$HVER 204" new "read list check" -expectpart "$(curl -u guest:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:a=key42)" 0 'HTTP/1.1 200 OK' '{"nacm-example:a":[{"k":"key42","b":{"c":"update"}}]} +expectpart "$(curl -u guest:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:a=key42)" 0 "HTTP/$HVER 200" '{"nacm-example:a":[{"k":"key42","b":{"c":"update"}}]} ' new "delete list deny" -expectpart "$(curl -u guest:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/nacm-example:a=key42)" 0 'HTTP/1.1 403 Forbidden' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}} ' +expectpart "$(curl -u guest:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/nacm-example:a=key42)" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}} ' new "delete list permit" -expectpart "$(curl -u wilma:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/nacm-example:a=key42)" 0 'HTTP/1.1 204 No Content' +expectpart "$(curl -u wilma:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/nacm-example:a=key42)" 0 "HTTP/$HVER 204" #----- default deny (clixon-example limit and guest have default access) new "default create list deny" -expectpart "$(curl -u wilma:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/clixon-example:table/parameter=key42 -d '{"clixon-example:parameter":[{"name":"key42","value":"val42"}]}')" 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 $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/clixon-example:table/parameter=key42 -d '{"clixon-example:parameter":[{"name":"key42","value":"val42"}]}')" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}' new "create list permit" -expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/clixon-example:table/parameter=key42 -d '{"clixon-example:parameter": [{"name":"key42","value":"val42"}]}')" 0 'HTTP/1.1 201 Created' +expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/clixon-example:table/parameter=key42 -d '{"clixon-example:parameter": [{"name":"key42","value":"val42"}]}')" 0 "HTTP/$HVER 201" new "default update list deny" -expectpart "$(curl -u wilma:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/clixon-example:table/parameter=key42 -d '{"clixon-example:parameter": [{"name":"key42","value":"val99"}]}')" 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 $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/clixon-example:table/parameter=key42 -d '{"clixon-example:parameter": [{"name":"key42","value":"val99"}]}')" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}' new "default delete list deny" -expectpart "$(curl -u wilma:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/clixon-example:table/parameter=key42)" 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 $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/clixon-example:table/parameter=key42)" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}' if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_nacm_protocol.sh b/test/test_nacm_protocol.sh index 938a1eda..1d093632 100755 --- a/test/test_nacm_protocol.sh +++ b/test/test_nacm_protocol.sh @@ -171,12 +171,12 @@ new "commit it" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" new "enable nacm" -expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/$HVER 204" #--------------- nacm enabled new "admin get nacm" -expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}' +expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 200" '{"nacm-example:x":0}' # Rule 1: deny-kill-session new "deny-kill-session: limited fail (netconf)" @@ -193,17 +193,17 @@ new "deny-delete-config: limited fail (netconf)" expecteof "$clixon_netconf -qf $cfg -U wilma" 0 "$DEFAULTHELLO]]>]]>" "^applicationaccess-deniederroraccess denied]]>]]>$" new "deny-delete-config: guest fail (restconf)" -expectpart "$(curl -u guest:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data)" 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 guest:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' # In restconf delete-config is translated to edit-config which is permitted new "deny-delete-config: limited fail (restconf) ok" -expectpart "$(curl -u wilma:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -u wilma:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 204" new "admin get nacm (should fail)" -expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 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 -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 404" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' new "deny-delete-config: admin ok (restconf)" -expectpart "$(curl -u andy:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -u andy:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 204" # Here the whole config is gone so we need to start again new "auth set authentication config (restart)" @@ -213,14 +213,14 @@ new "commit it" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" new "enable nacm" -expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/$HVER 204" # Rule 3: permit-edit-config new "permit-edit-config: limited ok restconf" -expectpart "$(curl -u wilma:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x":2}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -u wilma:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x":2}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 204" new "permit-edit-config: guest fail restconf" -expectpart "$(curl -u guest:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x":2}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 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 guest:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x":2}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_perf_restconf.sh b/test/test_perf_restconf.sh index 80f7c2ec..958930b2 100755 --- a/test/test_perf_restconf.sh +++ b/test/test_perf_restconf.sh @@ -100,11 +100,11 @@ if [ $RC -ne 0 ]; then new "start restconf daemon" start_restconf -f $cfg - - new "waiting" - wait_restconf fi +new "wait restconf" +wait_restconf + # Check this later with committed data new "generate config with $perfnr list entries" echo -n "" > $fconfigonly @@ -131,10 +131,11 @@ new "netconf commit large config" expecteof "time -p $clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" 2>&1 | awk '/real/ {print $2}' new "Check running-db contents" -curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/data?content=config > $foutput +curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/data?content=config > $foutput # Remove Content-Length line (depends on size) sed -i '/Content-Length:/d' $foutput +sed -i '/content-length:/d' $foutput # Remove (nginx) web-server specific lines sed -i '/Server:/d' $foutput sed -i '/Date:/d' $foutput @@ -142,7 +143,11 @@ sed -i '/Transfer-Encoding:/d' $foutput sed -i '/Connection:/d' $foutput # Create a file to compare with -echo "HTTP/1.1 200 OK " > $ftest +if ${WITH_HTTP2}; then + echo "HTTP/$HVER 200 " > $ftest +else + echo "HTTP/$HVER 200 OK " > $ftest +fi echo "Content-Type: application/yang-data+xml " >> $ftest echo "Cache-Control: no-cache " >> $ftest echo " ">> $ftest @@ -150,7 +155,7 @@ echo -n "">> $ftest cat $fconfigonly >> $ftest echo " " >> $ftest -ret=$(diff $ftest $foutput) +ret=$(diff -i $ftest $foutput) if [ $? -ne 0 ]; then err1 "Matching running-db with $fconfigonly" fi @@ -190,7 +195,6 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO /dev/null; } 2>&1 | awk '/real/ {print $2}' # RESTCONF get new "restconf get test single req" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:interfaces/a=foo/b/interface=e1)" 0 "HTTP/1.1 200 OK" '{"example:interface":\[{"name":"e1","type":"ex:eth","enabled":true,"status":"up"}\]}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:interfaces/a=foo/b/interface=e1)" 0 "HTTP/$HVER 200" '{"example:interface":\[{"name":"e1","type":"ex:eth","enabled":true,"status":"up"}\]}' new "restconf get $perfreq single reqs" #curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=e67 diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 5da8f40f..90f31067 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -169,23 +169,26 @@ function testrun() wait_restconf new "restconf root discovery. RFC 8040 3.1 (xml+xrd)" - expectpart "$(curl $CURLOPTS -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "" "" "" + expectpart "$(curl $CURLOPTS -X GET $proto://$addr/.well-known/host-meta)" 0 "HTTP/$HVER 200" "" "" "" + + if ! ${WITH_HTTP2}; then # http/2 if [ "${WITH_RESTCONF}" = "native" ]; then # XXX does not work with nginx new "restconf GET http/1.0 - returns 1.0" expectpart "$(curl $CURLOPTS --http1.0 -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.0 200 OK' "" "" "" - fi + fi new "restconf GET http/1.1" expectpart "$(curl $CURLOPTS --http1.1 -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "" "" "" + # Try http/2 - go back to http/1.1 new "restconf GET http/2" - expectpart "$(curl $CURLOPTS --http2 -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "" "" "" + expectpart "$(curl $CURLOPTS --http2 -X GET $proto://$addr/.well-known/host-meta)" 0 "HTTP/1.1 200 OK" "" "" "" if [ $proto = http ]; then # see (2) https to http port in restconf_main_native.c new "restconf GET http/2 prior-knowledge (http)" expectpart "$(curl $CURLOPTS --http2-prior-knowledge -X GET $proto://$addr/.well-known/host-meta 2>&1)" "16 52 55" # "Error in the HTTP2 framing layer" "Connection reset by peer" else - new "restconf GET http/2 prior-knowledge (https)" + new "restconf GET http/2 prior-knowledge(http2)" expectpart "$(curl $CURLOPTS --http2-prior-knowledge -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "" "" "" fi @@ -195,21 +198,21 @@ function testrun() expectpart "$(curl $CURLOPTS -X GET https://$addr:80/.well-known/host-meta 2>&1)" 35 #"wrong version number" # dependent on curl version else # see (1) http to https port in restconf_main_native.c new "Wrong proto=http on https port, expect bad request" - expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta)" 0 "HTTP/1.1 400 Bad Request" + expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta)" 0 "HTTP/$HVER 400" # expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta 2>&1)" 56 "Connection reset by peer" fi - + fi # HTTP/2 # Exact match new "restconf get restconf resource. RFC 8040 3.3 (json)" - expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $proto://$addr/restconf)" 0 'HTTP/1.1 200 OK' '{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2019-01-04"}}' + expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $proto://$addr/restconf)" 0 "HTTP/$HVER 200" '{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2019-01-04"}}' new "restconf get restconf resource. RFC 8040 3.3 (xml)" # Get XML instead of JSON? - expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $proto://$addr/restconf)" 0 'HTTP/1.1 200 OK' '2019-01-04' + expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $proto://$addr/restconf)" 0 "HTTP/$HVER 200" '2019-01-04' # Should be alphabetically ordered new "restconf get restconf/operations. RFC8040 3.3.2 (json)" - expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/operations)" 0 'HTTP/1.1 200 OK' '{"operations":{' '"clixon-example:client-rpc":\[null\],"clixon-example:empty":\[null\],"clixon-example:optional":\[null\],"clixon-example:example":\[null\]' '"clixon-lib:debug":\[null\],"clixon-lib:ping":\[null\],"clixon-lib:stats":\[null\],"clixon-lib:restart-plugin":\[null\]' '"ietf-netconf:get-config":\[null\],"ietf-netconf:edit-config":\[null\],"ietf-netconf:copy-config":\[null\],"ietf-netconf:delete-config":\[null\],"ietf-netconf:lock":\[null\],"ietf-netconf:unlock":\[null\],"ietf-netconf:get":\[null\],"ietf-netconf:close-session":\[null\],"ietf-netconf:kill-session":\[null\],"ietf-netconf:commit":\[null\],"ietf-netconf:discard-changes":\[null\],"ietf-netconf:validate":\[null\]' + expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/operations)" 0 "HTTP/$HVER 200" '{"operations":{' '"clixon-example:client-rpc":\[null\],"clixon-example:empty":\[null\],"clixon-example:optional":\[null\],"clixon-example:example":\[null\]' '"clixon-lib:debug":\[null\],"clixon-lib:ping":\[null\],"clixon-lib:stats":\[null\],"clixon-lib:restart-plugin":\[null\]' '"ietf-netconf:get-config":\[null\],"ietf-netconf:edit-config":\[null\],"ietf-netconf:copy-config":\[null\],"ietf-netconf:delete-config":\[null\],"ietf-netconf:lock":\[null\],"ietf-netconf:unlock":\[null\],"ietf-netconf:get":\[null\],"ietf-netconf:close-session":\[null\],"ietf-netconf:kill-session":\[null\],"ietf-netconf:commit":\[null\],"ietf-netconf:discard-changes":\[null\],"ietf-netconf:validate":\[null\]' new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" ret=$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $proto://$addr/restconf/operations) @@ -220,7 +223,7 @@ function testrun() fi new "restconf get restconf/yang-library-version. RFC8040 3.3.3" - expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/yang-library-version)" 0 'HTTP/1.1 200 OK' '{"yang-library-version":"2019-01-04"}' + expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/yang-library-version)" 0 "HTTP/$HVER 200" '{"yang-library-version":"2019-01-04"}' new "restconf get restconf/yang-library-version. RFC8040 3.3.3 (xml)" ret=$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $proto://$addr/restconf/yang-library-version) @@ -231,42 +234,41 @@ function testrun() fi new "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895 (explicit resource)" - expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-yang-library:modules-state/module=ietf-interfaces,2018-02-20)" 0 'HTTP/1.1 200 OK' '{"ietf-yang-library:module":\[{"name":"ietf-interfaces","revision":"2018-02-20","namespace":"urn:ietf:params:xml:ns:yang:ietf-interfaces","conformance-type":"implement"}\]}' + expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-yang-library:modules-state/module=ietf-interfaces,2018-02-20)" 0 "HTTP/$HVER 200" '{"ietf-yang-library:module":\[{"name":"ietf-interfaces","revision":"2018-02-20","namespace":"urn:ietf:params:xml:ns:yang:ietf-interfaces","conformance-type":"implement"}\]}' new "restconf schema resource, mod-state top-level" - expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-yang-library:modules-state)" 0 'HTTP/1.1 200 OK' "{\"ietf-yang-library:modules-state\":{\"module-set-id\":\"0\",\"module\":\[{\"name\":\"clixon-example\",\"revision\":\"${CLIXON_EXAMPLE_REV}\",\"namespace\":\"urn:example:clixon\",\"conformance-type\":\"implement\"},{\"name\":\"clixon-lib\",\"revision\":\"${CLIXON_LIB_REV}\",\"" + expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-yang-library:modules-state)" 0 "HTTP/$HVER 200" "{\"ietf-yang-library:modules-state\":{\"module-set-id\":\"0\",\"module\":\[{\"name\":\"clixon-example\",\"revision\":\"${CLIXON_EXAMPLE_REV}\",\"namespace\":\"urn:example:clixon\",\"conformance-type\":\"implement\"},{\"name\":\"clixon-lib\",\"revision\":\"${CLIXON_LIB_REV}\",\"" new "restconf options. RFC 8040 4.1" - expectpart "$(curl $CURLOPTS -X OPTIONS $proto://$addr/restconf/data)" 0 "HTTP/1.1 200 OK" "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE" + expectpart "$(curl $CURLOPTS -X OPTIONS $proto://$addr/restconf/data)" 0 "HTTP/$HVER 200" "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE" - # -I means HEAD new "restconf HEAD. RFC 8040 4.2" - expectpart "$(curl $CURLOPTS -I -H "Accept: application/yang-data+json" $proto://$addr/restconf/data)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+json" - + expectpart "$(curl $CURLOPTS --head -H "Accept: application/yang-data+json" $proto://$addr/restconf/data)" 0 "HTTP/$HVER 200" "Content-Type: application/yang-data+json" + new "restconf empty rpc JSON" - expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":null} $proto://$addr/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content" + expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":null} $proto://$addr/restconf/operations/clixon-example:empty)" 0 "HTTP/$HVER 204" new "restconf empty rpc XML" - expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" -d '' $proto://$addr/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content" + expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" -d '' $proto://$addr/restconf/operations/clixon-example:empty)" 0 "HTTP/$HVER 204" new "restconf empty rpc, default media type should fail" - expectpart "$(curl $CURLOPTS -X POST -d {\"clixon-example:input\":null} $proto://$addr/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type' + expectpart "$(curl $CURLOPTS -X POST -d {\"clixon-example:input\":null} $proto://$addr/restconf/operations/clixon-example:empty)" 0 "HTTP/$HVER 415" new "restconf empty rpc, default media type should fail (JSON)" - expectpart "$(curl $CURLOPTS -X POST -H "Accept: application/yang-data+json" -d {\"clixon-example:input\":null} $proto://$addr/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type' + expectpart "$(curl $CURLOPTS -X POST -H "Accept: application/yang-data+json" -d {\"clixon-example:input\":null} $proto://$addr/restconf/operations/clixon-example:empty)" 0 "HTTP/$HVER 415" new "restconf empty rpc with extra args (should fail)" - expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":{\"extra\":null}} $proto://$addr/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"Unrecognized parameter: extra in rpc: empty"}}}' + expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":{\"extra\":null}} $proto://$addr/restconf/operations/clixon-example:empty)" 0 "HTTP/$HVER 400" '{"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 $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-lib:input\":{\"level\":0}} $proto://$addr/restconf/operations/clixon-lib:debug)" 0 "HTTP/1.1 204 No Content" + #expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-lib:input\":{\"level\":0}} $proto://$addr/restconf/operations/clixon-lib:debug)" 0 "HTTP/$HVER 204" new "restconf get empty config + state json" - expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" '{"clixon-example:state":{"op":\["41","42","43"\]}}' + expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/clixon-example:state)" 0 "HTTP/$HVER 200" '{"clixon-example:state":{"op":\["41","42","43"\]}}' new "restconf get empty config + state json with wrong module name" - expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/badmodule:state)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"badmodule"},"error-severity":"error","error-message":"No such yang module prefix"}}}' + expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/badmodule:state)" 0 "HTTP/$HVER 400" '{"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"}}}' @@ -304,89 +306,89 @@ function testrun() fi new "restconf GET datastore" - expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" '{"clixon-example:state":{"op":\["41","42","43"\]}}' + expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/clixon-example:state)" 0 "HTTP/$HVER 200" '{"clixon-example:state":{"op":\["41","42","43"\]}}' # Exact match new "restconf Add subtree eth/0/0 to datastore using POST" - expectpart "$(curl $CURLOPTS -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $proto://$addr/restconf/data)" 0 'HTTP/1.1 201 Created' "Location: $proto://$addr/restconf/data/ietf-interfaces:interfaces" + expectpart "$(curl $CURLOPTS -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $proto://$addr/restconf/data)" 0 "HTTP/$HVER 201" "Location: $proto://$addr/restconf/data/ietf-interfaces:interfaces" # See test_json.sh new "restconf empty list" - expectpart "$(curl $CURLOPTS -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"clixon-example:table":{"parameter":[]}}' $proto://$addr/restconf/data)" 0 'HTTP/1.1 201 Created' "Location: $proto://$addr/restconf/data/clixon-example:table" + expectpart "$(curl $CURLOPTS -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"clixon-example:table":{"parameter":[]}}' $proto://$addr/restconf/data)" 0 "HTTP/$HVER 201" "Location: $proto://$addr/restconf/data/clixon-example:table" new "restconf Re-add subtree eth/0/0 which should give error" expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $proto://$addr/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}' new "restconf Check interfaces eth/0/0 added" - expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}' + expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/$HVER 200" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}' new "restconf delete interfaces" - expectpart "$(curl $CURLOPTS -X DELETE $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 204 No Content" + expectpart "$(curl $CURLOPTS -X DELETE $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/$HVER 204" new "restconf Check empty config" - expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" "$state" + expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/clixon-example:state)" 0 "HTTP/$HVER 200" "$state" new "restconf Add interfaces subtree eth/0/0 using POST" - expectpart "$(curl $CURLOPTS -X POST $proto://$addr/restconf/data/ietf-interfaces:interfaces -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}')" 0 "HTTP/1.1 201 Created" + expectpart "$(curl $CURLOPTS -X POST $proto://$addr/restconf/data/ietf-interfaces:interfaces -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}')" 0 "HTTP/$HVER 201" new "restconf Check eth/0/0 added config" - expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}' + expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/$HVER 200" '{"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 $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}' + expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/$HVER 200" '{"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 $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0/clixon-example:my-status)" 0 'HTTP/1.1 200 OK' '{"clixon-example:my-status":{"int":42,"str":"foo"}}' + expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0/clixon-example:my-status)" 0 "HTTP/$HVER 200" '{"clixon-example:my-status":{"int":42,"str":"foo"}}' new "restconf Check eth/0/0 added state XXXXXXX" - expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/clixon-example:state)" 0 'HTTP/1.1 200 OK' '{"clixon-example:state":{"op":\["41","42","43"\]}}' + expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/clixon-example:state)" 0 "HTTP/$HVER 200" '{"clixon-example:state":{"op":\["41","42","43"\]}}' new "restconf Re-post eth/0/0 which should generate error" expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}} ' new "Add leaf description using POST" - expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:description":"The-first-interface"}' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created" + expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:description":"The-first-interface"}' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/$HVER 201" new "Add nothing using POST (expect fail)" - expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"The message-body MUST contain exactly one instance of the expected data resource"}}}' + expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/$HVER 400" '{"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" - expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","description":"The-first-interface","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}' + expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/$HVER 200" '{"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 $CURLOPTS -X DELETE $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 204 No Content" + expectpart "$(curl $CURLOPTS -X DELETE $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/$HVER 204" new "Check deleted eth/0/0" - expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data)" 0 "HTTP/1.1 200 OK" "$state" + expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data)" 0 "HTTP/$HVER 200" "$state" new "restconf Re-Delete eth/0/0 using none should generate error" - expectpart "$(curl $CURLOPTS -X DELETE $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 409 Conflict" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-missing","error-severity":"error","error-message":"Data does not exist; cannot delete resource"}}}' + expectpart "$(curl $CURLOPTS -X DELETE $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/$HVER 409" '{"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 $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created" + expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/$HVER 201" new "restconf get subtree" - expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}' + expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/$HVER 200" '{"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" - expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' $proto://$addr/restconf/operations/clixon-example:example)" 0 "HTTP/1.1 200 OK" '{"clixon-example:output":{"x":"42","y":"42"}}' + expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' $proto://$addr/restconf/operations/clixon-example:example)" 0 "HTTP/$HVER 200" '{"clixon-example:output":{"x":"42","y":"42"}}' if ! $YANG_UNKNOWN_ANYDATA ; then new "restconf rpc using POST json wrong" - expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' $proto://$addr/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: wrongelement with parent: example in namespace: urn:example:clixon"}}}' + expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' $proto://$addr/restconf/operations/clixon-example:example)" 0 "HTTP/$HVER 400" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: wrongelement with parent: example in namespace: urn:example:clixon"}}}' fi new "restconf rpc non-existing rpc without namespace" - expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $proto://$addr/restconf/operations/kalle)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}' + expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $proto://$addr/restconf/operations/kalle)" 0 "HTTP/$HVER 400" '{"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 $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $proto://$addr/restconf/operations/clixon-example:kalle)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}' + expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $proto://$addr/restconf/operations/clixon-example:kalle)" 0 "HTTP/$HVER 400" '{"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 $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $proto://$addr/restconf/operations)" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"Operation name expected"}}}' + expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $proto://$addr/restconf/operations)" 0 "HTTP/$HVER 412" '{"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 $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $proto://$addr/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"restconf RPC does not have input statement"}}}' + expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $proto://$addr/restconf/operations/clixon-example:example)" 0 "HTTP/$HVER 400" '{"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 $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":42}}' $proto://$addr/restconf/operations/clixon-example:example) @@ -397,7 +399,7 @@ function testrun() fi new "restconf rpc using wrong prefix" - expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' $proto://$addr/restconf/operations/wrong:example)" 0 "HTTP/1.1 412 Precondition Failed" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"yang module not found"}}}' + expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' $proto://$addr/restconf/operations/wrong:example)" 0 "HTTP/$HVER 412" '{"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 $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":"example"}}' $proto://$addr/restconf/operations/clixon-example:client-rpc) @@ -408,10 +410,10 @@ function testrun() fi new "restconf Add subtree without key (expected error)" - expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =interface, expected' + expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface)" 0 "HTTP/$HVER 400" '{"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)" - expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 "HTTP/1.1 400 Bad Request" '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}}' + expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 "HTTP/$HVER 400" '{"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_restconf_basic_auth.sh b/test/test_restconf_basic_auth.sh index 3ab2ef80..7eb968c7 100755 --- a/test/test_restconf_basic_auth.sh +++ b/test/test_restconf_basic_auth.sh @@ -243,33 +243,33 @@ MSGERR2='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag AUTH=none new "auth-type=$AUTH no user" -testrun $AUTH "" "HTTP/1.1 200 OK" "$MSGANON" # OK - anonymous +testrun $AUTH "" "HTTP/$HPRE 200" "$MSGANON" # OK - anonymous new "auth-type=$AUTH anonymous" -testrun $AUTH "-u ${anonymous}:foo" "HTTP/1.1 200 OK" "$MSGANON" # OK - anonymous +testrun $AUTH "-u ${anonymous}:foo" "HTTP/$HVER 200" "$MSGANON" # OK - anonymous new "auth-type=$AUTH wilma" -testrun $AUTH "-u wilma:bar" "HTTP/1.1 200 OK" "$MSGANON" # OK - wilma +testrun $AUTH "-u wilma:bar" "HTTP/$HVER 200" # OK - wilma new "auth-type=$AUTH wilma wrong passwd" -testrun $AUTH "-u wilma:wrong" "HTTP/1.1 200 OK" "$MSGANON" # OK - wilma +testrun $AUTH "-u wilma:wrong" "HTTP/$HVER 200" "$MSGANON" # OK - wilma AUTH=user new "auth-type=$AUTH no user" -testrun $AUTH "" "HTTP/1.1 401 Unauthorized" "$MSGERR1" # denied +testrun $AUTH "" "HTTP/$HVER 401" "$MSGERR1" # denied new "auth-type=$AUTH anonymous" -testrun $AUTH "-u ${anonymous}:foo" "HTTP/1.1 401 Unauthorized" "$MSGERR1" # denied +testrun $AUTH "-u ${anonymous}:foo" "HTTP/$HVER 401" # denied new "auth-type=$AUTH wilma" -testrun $AUTH "-u wilma:bar" "HTTP/1.1 200 OK" "$MSGWILMA" # OK - wilma +testrun $AUTH "-u wilma:bar" "HTTP/$HVER 200" "$MSGWILMA" # OK - wilma new "auth-type=$AUTH wilma wrong passwd" -testrun $AUTH "-u wilma:wrong" "HTTP/1.1 401 Unauthorized" "$MSGERR1" # denied +testrun $AUTH "-u wilma:wrong" "HTTP/$HVER 401" "$MSGERR1" # denied new "auth-type=$AUTH unknown" -testrun $AUTH "-u unknown:any" "HTTP/1.1 401 Unauthorized" "$MSGERR1" # denied +testrun $AUTH "-u unknown:any" "HTTP/$HVER 401" "$MSGERR1" # denied if [ $BE -ne 0 ]; then new "Kill backend" diff --git a/test/test_restconf_err.sh b/test/test_restconf_err.sh index 3a7f541c..4756093e 100755 --- a/test/test_restconf_err.sh +++ b/test/test_restconf_err.sh @@ -187,16 +187,16 @@ new "wait restconf" wait_restconf new "restconf POST initial tree" -expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml' -d "$XML" $RCPROTO://localhost/restconf/data)" 0 'HTTP/1.1 201 Created' +expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml' -d "$XML" $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 201" new "restconf GET initial datastore" -expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example:a=0)" 0 'HTTP/1.1 200 OK' "$XML" +expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example:a=0)" 0 "HTTP/$HVER 200" "$XML" # XXX cannot get this to work for all combinations of nc/netcat fcgi/native # But leave it here for debugging where netcat works properly # Alt try something like: # printf "Hello World!" | (exec 3<>/dev/tcp/127.0.0.1/80; cat >&3; cat <&3; exec 3<&-) -if false; then +if [ false -a ! ${WITH_HTTP2} ] ; then # Look for netcat or nc for direct socket http calls if [ -n "$(type netcat 2> /dev/null)" ]; then netcat="netcat -w 1" # -N works on evhtp but not fcgi @@ -207,34 +207,34 @@ if false; then fi # new "restconf try fuzz crash" -# expectpart "$(${netcat} 127.0.0.1 80 < ~/tmp/crashes/id:000000,sig:06,src:000493+000365,op:splice,rep:8)" 0 "HTTP/1.1 400 Bad Request" +# expectpart "$(${netcat} 127.0.0.1 80 < ~/tmp/crashes/id:000000,sig:06,src:000493+000365,op:splice,rep:8)" 0 "HTTP/$HVER 400" new "restconf GET initial datastore netcat" expectpart "$(${netcat} 127.0.0.1 80 <malformed-messageThe requested URL or a header is in some way badly formed' +)" 0 "HTTP/$HVER 400" # native: 'malformed-messageThe requested URL or a header is in some way badly formed' -fi # netcat Cannot get to work on all platforms +fi # Http/1.1 and netcat Cannot get to work on all platforms new "restconf XYZ not found" -expectpart "$(curl $CURLOPTS -X XYS -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example:a=0)" 0 'HTTP/1.1 404 Not Found' +expectpart "$(curl $CURLOPTS -X XYS -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example:a=0)" 0 "HTTP/$HVER 404" -# XXX dont work w fcgi -if [ "${WITH_RESTCONF}" = "native" ]; then - new "restconf HEAD not allowed" - expectpart "$(curl $CURLOPTS -X HEAD -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf)" 0 "HTTP/1.1 405" "Not Allowed" "Allow: GET" -fi +new "restconf PUT not allowed" +expectpart "$(curl $CURLOPTS -X PUT $RCPROTO://localhost/.well-known/host-meta)" 0 "HTTP/$HVER 405" "Allow: GET,HEAD" new "restconf GET non-qualified list" -expectpart "$(curl $CURLOPTS -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'\"}}}" +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:a)" 0 "HTTP/$HVER 400" "{\"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 container with rest-api" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:table=x)" 0 'HTTP/1.1 400 Bad Request' "{\"ietf-restconf:errors\":{\"error\":{\"error-type\":\"rpc\",\"error-tag\":\"malformed-message\",\"error-severity\":\"error\",\"error-message\":\"malformed api-path, =x not expected\"}}}" +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:table=x)" 0 "HTTP/$HVER 400" "{\"ietf-restconf:errors\":{\"error\":{\"error-type\":\"rpc\",\"error-tag\":\"malformed-message\",\"error-severity\":\"error\",\"error-message\":\"malformed api-path, =x not expected\"}}}" new "restconf GET non-qualified list subelements" -expectpart "$(curl $CURLOPTS -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'\"}}}" +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:a/k)" 0 "HTTP/$HVER 400" "^{\"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 $CURLOPTS -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"}}}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:a=0/c)" 0 "HTTP/$HVER 404" '{"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 $CURLOPTS -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"}}}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:a=0/xxx)" 0 "HTTP/$HVER 400" '{"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 $CURLOPTS -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"}}}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:xxx)" 0 "HTTP/$HVER 400" '{"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 $CURLOPTS -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"}}}' +expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml' -d "$XML" $RCPROTO://localhost/restconf/data/example:a=23/xxx)" 0 "HTTP/$HVER 400" '{"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 $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml' -d '23' $RCPROTO://localhost/restconf/data)" 0 'HTTP/1.1 201 Created' +expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml' -d '23' $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 201" new "restconf GET augment multi-namespace top" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/augment:route-config)" 0 'HTTP/1.1 200 OK' '{"augment:route-config":{"dynamic":{"example:ospf":{"reference-bandwidth":23}}}}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/augment:route-config)" 0 "HTTP/$HVER 200" '{"augment:route-config":{"dynamic":{"example:ospf":{"reference-bandwidth":23}}}}' new "restconf GET augment multi-namespace level 1" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/augment:route-config/dynamic)" 0 'HTTP/1.1 200 OK' '{"augment:dynamic":{"example:ospf":{"reference-bandwidth":23}}}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/augment:route-config/dynamic)" 0 "HTTP/$HVER 200" '{"augment:dynamic":{"example:ospf":{"reference-bandwidth":23}}}' new "restconf GET augment multi-namespace cross" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/augment:route-config/dynamic/example:ospf)" 0 'HTTP/1.1 200 OK' '{"example:ospf":{"reference-bandwidth":23}}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/augment:route-config/dynamic/example:ospf)" 0 "HTTP/$HVER 200" '{"example:ospf":{"reference-bandwidth":23}}' new "restconf GET augment multi-namespace cross level 2" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/augment:route-config/dynamic/example:ospf/reference-bandwidth)" 0 'HTTP/1.1 200 OK' '{"example:reference-bandwidth":23}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/augment:route-config/dynamic/example:ospf/reference-bandwidth)" 0 "HTTP/$HVER 200" '{"example:reference-bandwidth":23}' # XXX actually no such element new "restconf GET augment multi-namespace, no 2nd module in api-path, fail" -expectpart "$(curl $CURLOPTS -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"}}}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/augment:route-config/dynamic/ospf)" 0 "HTTP/$HVER 404" '{"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 $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data?content=nonconfig)" 0 '412 Precondition Failed' 'applicationoperation-failedmystateerrorFailed to find YANG spec of XML node: mystate with parent: config in namespace: urn:example:foobar. Internal error, state callback returned invalid XML from plugin: example_backend' +expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data?content=nonconfig)" 0 "HTTP/$HVER 412" 'applicationoperation-failedmystateerrorFailed to find YANG spec of XML node: mystate with parent: config in namespace: urn:example:foobar. Internal error, state callback returned invalid XML from plugin: example_backend' # Add error XML a[4242] , it should fail on autocommit but may not be discarded, therefore still # there in candidate when want to add something else new "Add user-invalid entry (should fail)" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" $RCPROTO://localhost/restconf/data -d '4242
')" 0 "HTTP/1.1 412 Precondition Failed" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"operation-failed","error-severity":"error","error-message":"User error"}}}' +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" $RCPROTO://localhost/restconf/data -d '4242
')" 0 "HTTP/$HVER 412" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"operation-failed","error-severity":"error","error-message":"User error"}}}' new "Add OK entry" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" $RCPROTO://localhost/restconf/data -d '1
')" 0 "HTTP/1.1 201 Created" +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" $RCPROTO://localhost/restconf/data -d '1
')" 0 "HTTP/$HVER 201" if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_restconf_internal.sh b/test/test_restconf_internal.sh index 41e63bf4..bbde6eeb 100755 --- a/test/test_restconf_internal.sh +++ b/test/test_restconf_internal.sh @@ -215,7 +215,7 @@ new "wait restconf" wait_restconf new "try restconf rpc status" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/operations/clixon-lib:process-control -d '{"clixon-lib:input":{"name":"restconf","operation":"status"}}')" 0 "HTTP/1.1 200 OK" '{"clixon-lib:output":' '"active":' '"pid":' +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/operations/clixon-lib:process-control -d '{"clixon-lib:input":{"name":"restconf","operation":"status"}}')" 0 "HTTP/$HVER 200" '{"clixon-lib:output":' '"active":' '"pid":' new "2. Get status" rpcstatus true running @@ -228,7 +228,7 @@ if [ "$pid0" -ne "$pid1" ]; then fi new "try restconf rpc restart" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/operations/clixon-lib:process-control -d '{"clixon-lib:input":{"name":"restconf","operation":"restart"}}')" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/operations/clixon-lib:process-control -d '{"clixon-lib:input":{"name":"restconf","operation":"restart"}}')" 0 "HTTP/$HVER 204" new "3. Get status" rpcstatus true running @@ -420,7 +420,7 @@ wait_restconf # Edit a field, eg pretty to trigger a restart new "Edit a restconf field via restconf" # XXX fcgi fails here -expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/clixon-restconf:restconf/pretty -d '{"clixon-restconf:pretty":true}' )" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/clixon-restconf:restconf/pretty -d '{"clixon-restconf:pretty":true}' )" 0 "HTTP/$HVER 204" sleep $DEMSLEEP @@ -437,7 +437,7 @@ new "wait restconf" wait_restconf new "Edit a non-restconf field via restconf" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"example:val":"xyz"}' )" 0 "HTTP/1.1 201 Created" +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"example:val":"xyz"}' )" 0 "HTTP/$HVER 201" new "17. check status RPC same pid" rpcstatus true running diff --git a/test/test_restconf_internal_usecases.sh b/test/test_restconf_internal_usecases.sh index ea9096cd..70a94c37 100755 --- a/test/test_restconf_internal_usecases.sh +++ b/test/test_restconf_internal_usecases.sh @@ -129,7 +129,7 @@ EOF if [ -z "$pid" ]; then err "No pid return value" "$retx" fi -echo "retx:$retx" # XXX + if $active; then expect="^$activeClixon RESTCONF process$status20[0-9][0-9]\-[0-9][0-9]\-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\.[0-9]*Z$pid]]>]]>$" else @@ -181,17 +181,11 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" -echo "pid:$pid" # XXX -ps aux|grep clixon_ # XXX - new "2. get status, get pid1" rpcstatus true running pid1=$pid if [ $pid1 -eq 0 ]; then err "Pid" 0; fi -echo "pid1:$pid1" # XXX -ps aux|grep clixon_ # XXX - new "Check $pid1 exists" # Here backend dies / is killed # if sudo kill -0 $pid1; then # XXX @@ -199,14 +193,9 @@ while sudo kill -0 $pid1 2> /dev/null; do new "kill $pid1 externally" sudo kill $pid1 sleep 1 # There is a race condition here when restconf is killed while waiting for reply from backend - echo "pid1:$pid1" # XXX - ps aux|grep clixon_ # XXX done # fi -echo "pid1:$pid1" # XXX -ps aux|grep clixon_ # XXX - new "3. get status: Check killed" rpcstatus false stopped if [ $pid -ne 0 ]; then err "Pid" "$pid"; fi @@ -362,7 +351,7 @@ if [ $pid1 -eq 0 ]; then err "Pid" 0; fi sleep $DEMSLEEP new "Get restconf config 1" -expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/clixon-restconf:restconf)" 0 "HTTP/1.1 200 OK" "truenone$RESTCONFDBG$LOGDSTfalsefalsedefault
0.0.0.0
80false
" +expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/clixon-restconf:restconf)" 0 "HTTP/$HVER 200" "truenone$RESTCONFDBG$LOGDSTfalsefalsedefault
0.0.0.0
80false
" # remove it new "Delete server" @@ -457,7 +446,7 @@ if [ $pid1 -eq 0 ]; then err "Pid" 0; fi sleep $DEMSLEEP new "Get restconf config" -expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/clixon-restconf:restconf)" 0 "HTTP/1.1 200 OK" "truenone$RESTCONFDBG$LOGDSTfalsefalsedefault
0.0.0.0
80false
default
$INVALIDADDR
8080false
" +expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/clixon-restconf:restconf)" 0 "HTTP/$HVER 200" "truenone$RESTCONFDBG$LOGDSTfalsefalsedefault
0.0.0.0
80false
default
$INVALIDADDR
8080false
" if [ $BE -ne 0 ]; then new "Kill backend" diff --git a/test/test_restconf_jukebox.sh b/test/test_restconf_jukebox.sh index 9aa4ee74..29648dad 100755 --- a/test/test_restconf_jukebox.sh +++ b/test/test_restconf_jukebox.sh @@ -87,165 +87,163 @@ if [ $RC -ne 0 ]; then new "start restconf daemon" start_restconf -f $cfg - - fi new "wait restconf" wait_restconf new "B.1.1. Retrieve the Top-Level API Resource root" -expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/xrd+xml' $RCPROTO://localhost/.well-known/host-meta)" 0 "HTTP/1.1 200 OK" "Content-Type: application/xrd+xml" "" "" "" +expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/xrd+xml' $RCPROTO://localhost/.well-known/host-meta)" 0 "HTTP/$HVER 200" "Content-Type: application/xrd+xml" "" "" "" d='{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2019-01-04"}}' new "B.1.1. Retrieve the Top-Level API Resource /restconf json" -expectpart "$(curl $CURLOPTS -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" +expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf)" 0 "HTTP/$HVER 200" '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 $CURLOPTS -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" '2019-01-04' +expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf)" 0 "HTTP/$HVER 200" 'Cache-Control: no-cache' "Content-Type: application/yang-data+xml" '2019-01-04' # 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 $CURLOPTS -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\":\"${CLIXON_LIB_REV}\",\"namespace\":\"http://clicon.org/lib\",\"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"}' +expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-yang-library:modules-state)" 0 "HTTP/$HVER 200" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" "{\"ietf-yang-library:modules-state\":{\"module-set-id\":\"0\",\"module\":\[{\"name\":\"clixon-lib\",\"revision\":\"${CLIXON_LIB_REV}\",\"namespace\":\"http://clicon.org/lib\",\"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"}' new "B.1.3. Retrieve the Server Capability Information" -expectpart "$(curl $CURLOPTS -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 +expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/capabilities)" 0 "HTTP/$HVER 200" "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 $CURLOPTS -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: $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters" +expectpart "$(curl $CURLOPTS -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/$HVER 201" "Location: $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters" new "B.2.1. Create New Data Resources (album+xml)" -expectpart "$(curl $CURLOPTS -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: $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light" +expectpart "$(curl $CURLOPTS -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/$HVER 201" "Location: $RCPROTO://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 $CURLOPTS -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" +expectpart "$(curl $CURLOPTS -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/$HVER 409" new "4.5. PUT replace content (xml encoding)" -expectpart "$(curl $CURLOPTS -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" +expectpart "$(curl $CURLOPTS -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/$HVER 204" new "4.5. PUT create new identity" -expectpart "$(curl $CURLOPTS -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" +expectpart "$(curl $CURLOPTS -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/$HVER 201" new "4.5. Check jukebox content: 1 Clash and 1 Foo fighters album" -expectpart "$(curl $CURLOPTS -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' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" 'ClashLondon Calling1979Foo FightersWasting Lightjbox:alternative2011' new "B.2.2. Added genre (preamble to actual test)" -expectpart "$(curl $CURLOPTS -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" +expectpart "$(curl $CURLOPTS -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/$HVER 204" # First use of PATCH new "B.2.2. Detect Datastore Resource Entity-Tag Change (XXX if-unmodified)" -expectpart "$(curl $CURLOPTS -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' +expectpart "$(curl $CURLOPTS -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/$HVER 204" new "B.2.3. Edit a Datastore Resource (Add 1 Foo fighter and Nick cave album)" -expectpart "$(curl $CURLOPTS -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' +expectpart "$(curl $CURLOPTS -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/$HVER 204" new "B.2.3. Check patch system" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-system:system -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'true' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-system:system -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" 'true' new "B.2.3. Check jukebox: 1 Clash, 2 Foo Fighters, 1 Nick Cave" -expectpart "$(curl $CURLOPTS -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' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" 'ClashLondon Calling1979Foo FightersOne by One2012Wasting Lightalternative2011Nick Cave and the Bad SeedsTender Prey1988' new "B.2.4. Replace a Datastore Resource" -expectpart "$(curl $CURLOPTS -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" +expectpart "$(curl $CURLOPTS -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/$HVER 204" new "B.2.4. Check replace" -expectpart "$(curl $CURLOPTS -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' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" '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 $CURLOPTS -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' +expectpart "$(curl $CURLOPTS -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/$HVER 204" new "B.2.5. Check edit" -expectpart "$(curl $CURLOPTS -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' +expectpart "$(curl $CURLOPTS -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/$HVER 200" '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 $CURLOPTS -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" +expectpart "$(curl $CURLOPTS -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/$HVER 201" new 'B.3.1. "content" Parameter (wrong content)' -expectpart "$(curl $CURLOPTS -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"}}}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-events:events?content=kalle -H 'Accept: application/yang-data+json')" 0 "HTTP/$HVER 400" '{"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 $CURLOPTS -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}\]}}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-events:events?content=all -H 'Accept: application/yang-data+json')" 0 "HTTP/$HVER 200" '{"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 $CURLOPTS -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"}\]}}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-events:events?content=config -H 'Accept: application/yang-data+json')" 0 "HTTP/$HVER 200" '{"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 $CURLOPTS -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}\]}}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-events:events?content=nonconfig -H 'Accept: application/yang-data+json')" 0 "HTTP/$HVER 200" '{"example-events:events":{"event":\[{"name":"interface-down","event-count":90},{"name":"interface-up","event-count":77}\]}}' new 'B.3.2. "depth" Parameter example 1 unbound' -expectpart "$(curl $CURLOPTS -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}\]}\]}}}' +expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox?depth=unbounded)" 0 "HTTP/$HVER 200" '{"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 $CURLOPTS -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":{}}' +expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox?depth=1)" 0 "HTTP/$HVER 200" '{"example-jukebox:jukebox":{}}' new 'B.3.2. "depth" Parameter depth=2' -expectpart "$(curl $CURLOPTS -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":{}}}' +expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox?depth=2)" 0 "HTTP/$HVER 200" '{"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 $CURLOPTS -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\]}}} +expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox?depth=3)" 0 "HTTP/$HVER 200" '{"example-jukebox:jukebox":{"artist":\[null,null\]}}} ' new "restconf DELETE whole datastore" -expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 204" #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 $CURLOPTS -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: $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=1" +expectpart "$(curl $CURLOPTS -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/$HVER 201" "location: $RCPROTO://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 $CURLOPTS -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: $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=0" +expectpart "$(curl $CURLOPTS -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/$HVER 201" "Location: $RCPROTO://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'\]" -expectpart "$(curl $CURLOPTS -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" +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" "$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 $CURLOPTS -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: $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=2" +expectpart "$(curl $CURLOPTS -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/$HVER 201" "Location: $RCPROTO://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 $CURLOPTS -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" +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" "$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 $CURLOPTS -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" +expectpart "$(curl $CURLOPTS -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/$HVER 201" 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 $CURLOPTS -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" +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" "$RES" new "restconf DELETE whole datastore" -expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 204" new 'B.3.4. "insert/point" leaf-list 3 (not in RFC)' -expectpart "$(curl $CURLOPTS -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: $RCPROTO://localhost/restconf/data/example-jukebox:extra=3" +expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"3"}')" 0 "HTTP/$HVER 201" "Location: $RCPROTO://localhost/restconf/data/example-jukebox:extra=3" new 'B.3.4. "insert/point" leaf-list 2 first' -expectpart "$(curl $CURLOPTS -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: $RCPROTO://localhost/restconf/data/example-jukebox:extra=2" +expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data?insert=first -d '{"example-jukebox:extra":"2"}')" 0 "HTTP/$HVER 201" "Location: $RCPROTO://localhost/restconf/data/example-jukebox:extra=2" new 'B.3.4. "insert/point" leaf-list 1 last' -expectpart "$(curl $CURLOPTS -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: $RCPROTO://localhost/restconf/data/example-jukebox:extra=1" +expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/$HVER 201" "Location: $RCPROTO://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 $CURLOPTS -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: $RCPROTO://localhost/restconf/data/example-jukebox:extra=1" +#expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/$HVER 201" "Location: $RCPROTO://localhost/restconf/data/example-jukebox:extra=1" new 'B.3.5. "insert/point" leaf-list check order (2,3,1)' -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '231' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" '231' new 'B.3.5. "point" Parameter leaf-list 4 before 3' -expectpart "$(curl $CURLOPTS -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: $RCPROTO://localhost/restconf/data/example-jukebox:extra=4" +expectpart "$(curl $CURLOPTS -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/$HVER 201" "Location: $RCPROTO://localhost/restconf/data/example-jukebox:extra=4" new 'B.3.5. "insert/point" leaf-list check order (2,4,3,1)' -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '2431' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" '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 c28eb847..30a674c1 100755 --- a/test/test_restconf_listkey.sh +++ b/test/test_restconf_listkey.sh @@ -98,119 +98,119 @@ new "wait restconf" wait_restconf new "restconf PUT add whole list entry" -expectpart "$(curl $CURLOPTS -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 "HTTP/1.1 201 Created" +expectpart "$(curl $CURLOPTS -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 "HTTP/$HVER 201" # GETs to ensure you get list [] in JSON new "restconf GET whole list entry" -expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/)" 0 "HTTP/1.1 200 OK" '{"list:c":{"a":\[{"b":"x","c":"y","nonkey":"0"}\]}}' +expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/)" 0 "HTTP/$HVER 200" '{"list:c":{"a":\[{"b":"x","c":"y","nonkey":"0"}\]}}' new "restconf GET list entry itself (should fail)" -expectpart "$(curl $CURLOPTS -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 ' +expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a)" 0 "HTTP/$HVER 400" '^{"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" -expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y)" 0 "HTTP/1.1 200 OK" '{"list:a":\[{"b":"x","c":"y","nonkey":"0"}\]}' +expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y)" 0 "HTTP/$HVER 200" '{"list:a":\[{"b":"x","c":"y","nonkey":"0"}\]}' new "restconf PUT add whole list entry XML" -expectpart "$(curl $CURLOPTS -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 "HTTP/1.1 201 Created" +expectpart "$(curl $CURLOPTS -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 "HTTP/$HVER 201" new "restconf GET sub key" -expectpart "$(curl $CURLOPTS -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 ' +expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a/b)" 0 "HTTP/$HVER 400" '^{"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)" -expectpart "$(curl $CURLOPTS -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 "HTTP/1.1 204 No Content" +expectpart "$(curl $CURLOPTS -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 "HTTP/$HVER 204" new "restconf PUT change whole list entry (no namespace)(expect fail)" -expectpart "$(curl $CURLOPTS -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"}}} ' +expectpart "$(curl $CURLOPTS -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/$HVER 400" '{"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 $CURLOPTS -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"}}} ' +expectpart "$(curl $CURLOPTS -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 "HTTP/$HVER 412" '{"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" -expectpart "$(curl $CURLOPTS -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 "HTTP/1.1 412 Precondition Failed" 'protocoloperation-failederrorapi-path keys do not match data keys ' +expectpart "$(curl $CURLOPTS -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 "HTTP/$HVER 412" 'protocoloperation-failederrorapi-path keys do not match data keys ' new "restconf PUT change list entry (just one key)(expect fail)" -expectpart "$(curl $CURLOPTS -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"}}} ' +expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x -d '{"list:a":{"b":"x"}}')" 0 "HTTP/$HVER 400" '{"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" -expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/nonkey -d '{"list:nonkey":"u"}')" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/nonkey -d '{"list:nonkey":"u"}')" 0 "HTTP/$HVER 204" new "restconf PUT sub key same value" -expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/b -d '{"list:b":"x"}')" 0 "204 No Content" +expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/b -d '{"list:b":"x"}')" 0 "HTTP/$HVER 204" new "restconf PUT just key other value (should fail)" -expectpart "$(curl $CURLOPTS -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"}}}' +expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x/b -d '{"b":"y"}')" 0 "HTTP/$HVER 400" '{"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" -expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/d=x -d '{"list:d":"x"}')" 0 "HTTP/1.1 201 Created" +expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/d=x -d '{"list:d":"x"}')" 0 "HTTP/$HVER 201" new "restconf PUT change leaf-list entry (expect fail)" -expectpart "$(curl $CURLOPTS -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"}}}' +expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/d=x -d '{"list:d":"y"}')" 0 "HTTP/$HVER 412" '{"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" -expectpart "$(curl $CURLOPTS -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 "HTTP/1.1 201 Created" +expectpart "$(curl $CURLOPTS -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 "HTTP/$HVER 201" new "restconf GET e element with empty string key expect empty" -expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=)" 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 $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=)" 0 "HTTP/$HVER 404" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' new "restconf PUT list-list empty string" -expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e= -d '{"list:e":{"f":"","nonkey":"42"}}')" 0 "HTTP/1.1 201 Created" +expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e= -d '{"list:e":{"f":"","nonkey":"42"}}')" 0 "HTTP/$HVER 201" new "restconf GET e element with empty string key" -expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=)" 0 "HTTP/1.1 200 OK" '42' +expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=)" 0 "HTTP/$HVER 200" '42' new "restconf PUT change list-lst entry (wrong keys)(expect fail)" -expectpart "$(curl $CURLOPTS -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 "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 $CURLOPTS -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 "HTTP/$HVER 412" '{"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" -expectpart "$(curl $CURLOPTS -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 "HTTP/1.1 204 No Content" +expectpart "$(curl $CURLOPTS -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 "HTTP/$HVER 204" new "restconf PUT list-list single first key" -expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x/e=z/f -d '{"f":"z"}')" 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 $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x/e=z/f -d '{"f":"z"}')" 0 "HTTP/$HVER 400" '{"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" -expectpart "$(curl $CURLOPTS -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 "HTTP/1.1 204 No Content" +expectpart "$(curl $CURLOPTS -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 "HTTP/$HVER 204" new "restconf PUT list-list just key just key wrong value (should fail)" -expectpart "$(curl $CURLOPTS -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"}}} ' +expectpart "$(curl $CURLOPTS -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/$HVER 412" '{"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" -expectpart "$(curl $CURLOPTS -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 "HTTP/1.1 201 Created" +expectpart "$(curl $CURLOPTS -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 "HTTP/$HVER 201" new "restconf PUT change list+leaf-list entry (expect fail)" expectpart "$(curl $CURLOPTS -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"}}}' new "restconf PUT (x,y),z -> x%2Cy,z percent-encoded" -expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x%2Cy,z/nonkey -d '{"list:nonkey":"foo"}')" 0 "HTTP/1.1 201 Created" +expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x%2Cy,z/nonkey -d '{"list:nonkey":"foo"}')" 0 "HTTP/$HVER 201" new "restconf GET check percent-encoded" -expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x%2Cy,z)" 0 "HTTP/1.1 200 OK" '{"list:a":\[{"b":"x,y","c":"z","nonkey":"foo"}\]}' +expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x%2Cy,z)" 0 "HTTP/$HVER 200" '{"list:a":\[{"b":"x,y","c":"z","nonkey":"foo"}\]}' new "restconf PUT ,z empty string as first key" -expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=,z -d '{"list:a":{"b":"","c":"z", "nonkey":"42"}}')" 0 "HTTP/1.1 201 Created" +expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=,z -d '{"list:a":{"b":"","c":"z", "nonkey":"42"}}')" 0 "HTTP/$HVER 201" new "restconf GET ,z empty" -expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=,z)" 0 "HTTP/1.1 200 OK" '{"list:a":\[{"b":"","c":"z","nonkey":"42"}\]}' +expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=,z)" 0 "HTTP/$HVER 200" '{"list:a":\[{"b":"","c":"z","nonkey":"42"}\]}' new "restconf PUT x, empty string as second key" -expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x, -d '{"list:a":{"b":"x","c":"", "nonkey":"43"}}')" 0 "HTTP/1.1 201 Created" +expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x, -d '{"list:a":{"b":"x","c":"", "nonkey":"43"}}')" 0 "HTTP/$HVER 201" new "restconf GET x, empty" -expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,)" 0 "HTTP/1.1 200 OK" '{"list:a":\[{"b":"x","c":"","nonkey":"43"}\]}' +expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,)" 0 "HTTP/$HVER 200" '{"list:a":\[{"b":"x","c":"","nonkey":"43"}\]}' new "restconf PUT , empty string as second key" -expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=, -d '{"list:a":{"b":"","c":"", "nonkey":"44"}}')" 0 "HTTP/1.1 201 Created" +expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=, -d '{"list:a":{"b":"","c":"", "nonkey":"44"}}')" 0 "HTTP/$HVER 201" new "restconf GET , empty" -expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=,)" 0 "HTTP/1.1 200 OK" '{"list:a":\[{"b":"","c":"","nonkey":"44"}\]}' +expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=,)" 0 "HTTP/$HVER 200" '{"list:a":\[{"b":"","c":"","nonkey":"44"}\]}' new "restconf PUT two commas as keys" -expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=%2C,%2C -d '{"list:a":{"b":",","c":",", "nonkey":"45"}}')" 0 "HTTP/1.1 201 Created" +expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=%2C,%2C -d '{"list:a":{"b":",","c":",", "nonkey":"45"}}')" 0 "HTTP/$HVER 201" new "restconf GET two commas" -expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=%2C,%2C)" 0 "HTTP/1.1 200 OK" '{"list:a":\[{"b":",","c":",","nonkey":"45"}\]}' +expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=%2C,%2C)" 0 "HTTP/$HVER 200" '{"list:a":\[{"b":",","c":",","nonkey":"45"}\]}' new "restconf GET all empty strings" -expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c)" 0 "HTTP/1.1 200 OK" '{"list:c":{"a":\[{"b":"","c":"","nonkey":"44"},{"b":"","c":"z","nonkey":"42"},{"b":",","c":",","nonkey":"45"},{"b":"x","c":"","nonkey":"43"}' +expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c)" 0 "HTTP/$HVER 200" '{"list:c":{"a":\[{"b":"","c":"","nonkey":"44"},{"b":"","c":"z","nonkey":"42"},{"b":",","c":",","nonkey":"45"},{"b":"x","c":"","nonkey":"43"}' if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_restconf_netns.sh b/test/test_restconf_netns.sh index 936c6cc8..c632545f 100755 --- a/test/test_restconf_netns.sh +++ b/test/test_restconf_netns.sh @@ -8,7 +8,6 @@ # 1. Configure one valid and one invalid address in other dataplane # 2. Two restconf processes, one may be zombie - # Magic line must be first in script (see README.md) s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi @@ -175,19 +174,19 @@ new "netconf commit" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" new "restconf http get config on default netns" -expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' http://127.0.0.1/restconf/data/clixon-example:table)" 0 "HTTP/1.1 200 OK" 'a42
' +expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' http://127.0.0.1/restconf/data/clixon-example:table)" 0 "HTTP/$HVER 200" 'a42
' new "restconf http get config on addr:$vaddr in netns:$netns" -expectpart "$(sudo ip netns exec $netns curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' https://$vaddr/restconf/data/clixon-example:table)" 0 "HTTP/1.1 200 OK" 'a42
' +expectpart "$(sudo ip netns exec $netns curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' https://$vaddr/restconf/data/clixon-example:table)" 0 "HTTP/$HVER 200" 'a42
' new "restconf https/SSL get config on addr:$vaddr in netns:$netns" -expectpart "$(sudo ip netns exec $netns curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' https://$vaddr/restconf/data/clixon-example:table)" 0 "HTTP/1.1 200 OK" 'a42
' +expectpart "$(sudo ip netns exec $netns curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' https://$vaddr/restconf/data/clixon-example:table)" 0 "HTTP/$HVER 200" 'a42
' new "restconf https/SSL put table b" -expectpart "$(sudo ip netns exec $netns curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml' -d 'b99' https://$vaddr/restconf/data/clixon-example:table)" 0 "HTTP/1.1 201 Created" +expectpart "$(sudo ip netns exec $netns curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml' -d 'b99' https://$vaddr/restconf/data/clixon-example:table)" 0 "HTTP/$HVER 201" new "restconf http get table b on default ns" -expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' http://127.0.0.1/restconf/data/clixon-example:table/parameter=b)" 0 "HTTP/1.1 200 OK" 'b99' +expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' http://127.0.0.1/restconf/data/clixon-example:table/parameter=b)" 0 "HTTP/$HVER 200" 'b99' # Negative new "restconf get config on wrong port in netns:$netns" diff --git a/test/test_restconf_nmap.sh b/test/test_restconf_nmap.sh index a31b5f64..6aff68ea 100755 --- a/test/test_restconf_nmap.sh +++ b/test/test_restconf_nmap.sh @@ -185,7 +185,7 @@ new "nmap sslv2" expectpart "$(nmap --script sslv2 -p 443 127.0.0.1)" 0 "443/tcp open https" new "restconf get. Just ensure restconf is alive" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://127.0.0.1/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "" "" "" +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://127.0.0.1/.well-known/host-meta)" 0 "HTTP/$HVER 200" "" "" "" if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_restconf_notifications.sh b/test/test_restconf_notifications.sh index 3f9c04e8..e1b4e374 100755 --- a/test/test_restconf_notifications.sh +++ b/test/test_restconf_notifications.sh @@ -152,11 +152,11 @@ expecteof "$clixon_netconf -D $DBG -qf $cfg" 0 "$DEFAULTHELLO' 'HTTP/1.1 201 Created' +expectpart "$(curl $CURLOPTS -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data?content=config --next $CURLOPTS -H "Content-Type: application/yang-data+json" -X POST $RCPROTO://localhost/restconf/data -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}')" 0 "HTTP/$HVER 200" '' "HTTP/$HVER 201" #--------------- json type tests new "restconf POST type x3 POST" -expectpart "$(curl $CURLOPTS -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 "HTTP/1.1 201 Created" "Location: $RCPROTO://localhost/restconf/data/example:types" +expectpart "$(curl $CURLOPTS -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 "HTTP/$HVER 201" "Location: $RCPROTO://localhost/restconf/data/example:types" new "restconf POST type x3 GET" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:types)" 0 "HTTP/1.1 200 OK" '{"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:types)" 0 "HTTP/$HVER 200" '{"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_patch.sh b/test/test_restconf_patch.sh index 4351cdb9..9f3531f7 100755 --- a/test/test_restconf_patch.sh +++ b/test/test_restconf_patch.sh @@ -127,28 +127,28 @@ wait_restconf # also in test_restconf.sh new "MUST support the PATCH method for a plain patch" -expectpart "$(curl -u andy:bar $CURLOPTS -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" +expectpart "$(curl -u andy:bar $CURLOPTS -X OPTIONS $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 200" "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 $CURLOPTS -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 409 Conflict" "If the target resource instance does not exist, the server MUST NOT create it" +expectpart "$(curl -u andy:bar $CURLOPTS -X PATCH -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -d '{"example-jukebox:jukebox":null}')" 0 "HTTP/$HVER 409" "If the target resource instance does not exist, the server MUST NOT create it" new "Create it with PUT instead" -expectpart "$(curl -u andy:bar $CURLOPTS -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" +expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -d '{"example-jukebox:jukebox":null}')" 0 "HTTP/$HVER 201" new "THEN change it with PATCH" -expectpart "$(curl -u andy:bar $CURLOPTS -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" +expectpart "$(curl -u andy:bar $CURLOPTS -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/$HVER 204" new "Check content (json)" -expectpart "$(curl -u andy:bar $CURLOPTS -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"}\]}}}' +expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+json')" 0 "HTTP/$HVER 200" '{"example-jukebox:jukebox":{"library":{"artist":\[{"name":"Clash"}\]}}}' new "Check content (xml)" -expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'Clash' +expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" 'Clash' new 'If the user is not authorized, "403 Forbidden" SHOULD be returned.' -expectpart "$(curl -u wilma:bar $CURLOPTS -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"}}}' +expectpart "$(curl -u wilma:bar $CURLOPTS -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/$HVER 403" '{"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 $CURLOPTS -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" +expectpart "$(curl -u andy:bar $CURLOPTS -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/$HVER 204" # Kill old if [ $RC -ne 0 ]; then @@ -190,51 +190,51 @@ wait_restconf # 4.6.1. Plain Patch new "Create album London Calling with PUT" -expectpart "$(curl -u andy:bar $CURLOPTS -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" +expectpart "$(curl -u andy:bar $CURLOPTS -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/$HVER 201" new "The message-body for a plain patch MUST be present" -expectpart "$(curl -u andy:bar $CURLOPTS -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" +expectpart "$(curl -u andy:bar $CURLOPTS -X PATCH -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Beatles -d '')" 0 "HTTP/$HVER 400" # 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 $CURLOPTS -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' +expectpart "$(curl -u andy:bar $CURLOPTS -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/$HVER 204" new "Update a child resource (year)" -expectpart "$(curl -u andy:bar $CURLOPTS -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' +expectpart "$(curl -u andy:bar $CURLOPTS -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/$HVER 204" new "Check content xml" -expectpart "$(curl -u andy:bar $CURLOPTS -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' +expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" 'London Callingrock1979' new "Check content json" -expectpart "$(curl -u andy:bar $CURLOPTS -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}\]}' +expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -H 'Accept: application/yang-data+json')" 0 "HTTP/$HVER 200" '{"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 $CURLOPTS -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" +expectpart "$(curl -u andy:bar $CURLOPTS -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/$HVER 204" new "Check content (xml)" -expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://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 $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" 'ClashLondon Callingjazz1979' new "not implemented media type" -expectpart "$(curl -u andy:bar $CURLOPTS -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" +expectpart "$(curl -u andy:bar $CURLOPTS -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/$HVER 501" new "wrong media type" -expectpart "$(curl -u andy:bar $CURLOPTS -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" +expectpart "$(curl -u andy:bar $CURLOPTS -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/$HVER 415" # 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 $CURLOPTS -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" +expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:extra=a -d '{"example-jukebox:extra":"a"}')" 0 "HTTP/$HVER 201" new "Create leaf-list b" -expectpart "$(curl -u andy:bar $CURLOPTS -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" +expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:extra=b -d '{"example-jukebox:extra":"b"}')" 0 "HTTP/$HVER 201" new "Check content" -expectpart "$(curl -u andy:bar $CURLOPTS -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"\]}' +expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+json')" 0 "HTTP/$HVER 200" '{"example-jukebox:extra":\["a","b"\]}' new "MUST NOT change the value of the leaf-list instance" -expectpart "$(curl -u andy:bar $CURLOPTS -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' +expectpart "$(curl -u andy:bar $CURLOPTS -X PATCH -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:extra=a -d '{"example-jukebox:extra":"b"}')" 0 "HTTP/$HVER 412" # If the target resource represents a YANG list instance, then the key # leaf values, in message-body representation, MUST be the same as the @@ -242,13 +242,13 @@ expectpart "$(curl -u andy:bar $CURLOPTS -X PATCH -H 'Content-Type: application/ # 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 $CURLOPTS -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' +expectpart "$(curl -u andy:bar $CURLOPTS -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/$HVER 412" new "PATCH on root resource extra c" # merge extra/c -expectpart "$(curl -u andy:bar $CURLOPTS -X PATCH -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"ietf-restconf:data":{"example-jukebox:extra":"c"}}')" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -u andy:bar $CURLOPTS -X PATCH -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"ietf-restconf:data":{"example-jukebox:extra":"c"}}')" 0 "HTTP/$HVER 204" new "GET check" # XXX: "data" should probably be namespaced? -expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data?content=config -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'c' '' +expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data?content=config -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" 'c' '' if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_restconf_ssl_certs.sh b/test/test_restconf_ssl_certs.sh index f1ae0eba..dec1b1b3 100755 --- a/test/test_restconf_ssl_certs.sh +++ b/test/test_restconf_ssl_certs.sh @@ -247,7 +247,8 @@ EOF if [ $RC -ne 0 ]; then new "kill old restconf daemon" stop_restconf_pre - new "start restconf daemon -s -c" + + new "start restconf daemon" start_restconf -f $cfg fi @@ -255,22 +256,22 @@ EOF wait_restconf --key $certdir/andy.key --cert $certdir/andy.crt new "enable nacm" - expectpart "$(curl $CURLOPTS --key $certdir/andy.key --cert $certdir/andy.crt -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/1.1 204 No Content" + expectpart "$(curl $CURLOPTS --key $certdir/andy.key --cert $certdir/andy.crt -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/$HVER 204" new "admin get x" - expectpart "$(curl $CURLOPTS --key $certdir/andy.key --cert $certdir/andy.crt -X GET $RCPROTO://localhost/restconf/data/example:x)" 0 "HTTP/1.1 200 OK" '{"example:x":0}' + expectpart "$(curl $CURLOPTS --key $certdir/andy.key --cert $certdir/andy.crt -X GET $RCPROTO://localhost/restconf/data/example:x)" 0 "HTTP/$HVER 200" '{"example:x":0}' new "guest get x" - expectpart "$(curl $CURLOPTS --key $certdir/guest.key --cert $certdir/guest.crt -X GET $RCPROTO://localhost/restconf/data/example:x)" 0 "HTTP/1.1 403 Forbidden" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' + expectpart "$(curl $CURLOPTS --key $certdir/guest.key --cert $certdir/guest.crt -X GET $RCPROTO://localhost/restconf/data/example:x)" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' new "admin set x 42" - expectpart "$(curl $CURLOPTS --key $certdir/andy.key --cert $certdir/andy.crt -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:x":42}' $RCPROTO://localhost/restconf/data/example:x)" 0 "HTTP/1.1 204 No Content" + expectpart "$(curl $CURLOPTS --key $certdir/andy.key --cert $certdir/andy.crt -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:x":42}' $RCPROTO://localhost/restconf/data/example:x)" 0 "HTTP/$HVER 204" new "admin set x 42 without media" - expectpart "$(curl $CURLOPTS --key $certdir/andy.key --cert $certdir/andy.crt -X PUT -d '{"example:x":42}' $RCPROTO://localhost/restconf/data/example:x)" 0 "HTTP/1.1 415 Unsupported Media Type" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-not-supported","error-severity":"error","error-message":"Unsupported Media Type"}}}' + expectpart "$(curl $CURLOPTS --key $certdir/andy.key --cert $certdir/andy.crt -X PUT -d '{"example:x":42}' $RCPROTO://localhost/restconf/data/example:x)" 0 "HTTP/$HVER 415" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-not-supported","error-severity":"error","error-message":"Unsupported Media Type"}}}' new "admin get x 42" - expectpart "$(curl $CURLOPTS --key $certdir/andy.key --cert $certdir/andy.crt -X GET $RCPROTO://localhost/restconf/data/example:x)" 0 "HTTP/1.1 200 OK" '{"example:x":42}' + expectpart "$(curl $CURLOPTS --key $certdir/andy.key --cert $certdir/andy.crt -X GET $RCPROTO://localhost/restconf/data/example:x)" 0 "HTTP/$HVER 200" '{"example:x":42}' # Negative tests new "Unknown yyy no cert get x 42" @@ -280,7 +281,11 @@ EOF # See (3) client-cert is NULL in restconf_main_openssl.c new "No cert: certificate required" - expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:x 2>&1)" 0 "HTTP/1.1 400 Bad Request" + expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:x 2>&1)" 0 "HTTP/$HVER 401" + # Unsure if clixon should fail early with SSL_VERIFY_FAIL_IF_NO_PEER_CERT, instead now fail later in + # code +# expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:x 2>&1)" 0 "HTTP/$HVER 400" + new "limited invalid cert" expectpart "$(curl $CURLOPTS --key $certdir/limited.key --cert $certdir/limited.crt -X GET $RCPROTO://localhost/restconf/data/example:x 2>&1)" "35 55 56" # 55 "certificate expired" @@ -294,7 +299,7 @@ EOF # Just ensure all is OK new "admin get x 42" - expectpart "$(curl $CURLOPTS --key $certdir/andy.key --cert $certdir/andy.crt -X GET $RCPROTO://localhost/restconf/data/example:x)" 0 "HTTP/1.1 200 OK" '{"example:x":42}' + expectpart "$(curl $CURLOPTS --key $certdir/andy.key --cert $certdir/andy.crt -X GET $RCPROTO://localhost/restconf/data/example:x)" 0 "HTTP/$HVER 200" '{"example:x":42}' if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_restconf_startup.sh b/test/test_restconf_startup.sh index 91a39b0b..9c1a0841 100755 --- a/test/test_restconf_startup.sh +++ b/test/test_restconf_startup.sh @@ -81,16 +81,16 @@ function testrun(){ wait_restconf new "restconf put 42" - expectpart "$(curl $CURLOPTS -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 "HTTP/1.1 201 Created" + expectpart "$(curl $CURLOPTS -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 "HTTP/$HVER 201" new "restconf put 99" - expectpart "$(curl $CURLOPTS -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 "HTTP/1.1 201 Created" + expectpart "$(curl $CURLOPTS -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 "HTTP/$HVER 201" new "restconf post 123" - expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:x -d '{"example:y":{"a":"123","b":"123"}}')" 0 "HTTP/1.1 201 Created" + expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:x -d '{"example:y":{"a":"123","b":"123"}}')" 0 "HTTP/$HVER 201" new "restconf delete 42" - expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example:x/y=42)" 0 "HTTP/1.1 204 No Content" + expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example:x/y=42)" 0 "HTTP/$HVER 204" if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_rpc.sh b/test/test_rpc.sh index 0181f66c..153906ac 100755 --- a/test/test_rpc.sh +++ b/test/test_rpc.sh @@ -66,7 +66,7 @@ new "rpc tests" # extra complex because pattern matching on return haders new "restconf empty rpc" ret=$(curl $CURLOPTS -X POST $RCPROTO://localhost/restconf/operations/clixon-example:empty) -expect="204 No Content" +expect="HTTP/$HVER 204" match=`echo $ret | grep --null -Eo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" @@ -76,7 +76,7 @@ new "netconf empty rpc" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" new "restconf example rpc json/json default - no http media headers" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":0}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 200 OK' 'Content-Type: application/yang-data+json' '{"clixon-example:output":{"x":"0","y":"42"}}' +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":0}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 "HTTP/$HVER 200" 'Content-Type: application/yang-data+json' '{"clixon-example:output":{"x":"0","y":"42"}}' new "restconf example rpc json/json change y default" expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"0","y":"99"}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'Content-Type: application/yang-data+json' '{"clixon-example:output":{"x":"0","y":"99"}}' @@ -95,10 +95,10 @@ new "restconf example rpc xml/xml" expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '0' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'Content-Type: application/yang-data+xml' '042' new "restconf example rpc xml in w json encoding (expect fail)" -expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' -H 'Accept: application/yang-data+xml' -d '0' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' "rpcmalformed-messageerrorjson_parse: line 1: syntax error at or before: '<'" +expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' -H 'Accept: application/yang-data+xml' -d '0' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 "HTTP/$HVER 400" "rpcmalformed-messageerrorjson_parse: line 1: syntax error at or before: '<'" new "restconf example rpc json in xml encoding (expect fail)" -expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '{"clixon-example:input":{"x":"0"}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Reques' 'rpcmalformed-messageerrorxml_parse: line 0: syntax error: at or before: "' +expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '{"clixon-example:input":{"x":"0"}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 "HTTP/$HVER 400" 'rpcmalformed-messageerrorxml_parse: line 0: syntax error: at or before: "' new "netconf example rpc" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO0]]>]]>" "^042]]>]]>$" @@ -107,37 +107,37 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationmissing-elementsession-iderrorMandatory variable of kill-session in module ietf-netconf]]>]]>$" @@ -164,7 +164,7 @@ new "netconf wrong rpc namespace: should fail" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^applicationunknown-elementgeterrorUnrecognized RPC (wrong namespace?)]]>]]>$" new "restconf wrong rpc: should fail" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/operations/clixon-foo:get)" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"yang module not found"}}}' +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/operations/clixon-foo:get)" 0 "HTTP/$HVER 412" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"yang module not found"}}}' # test rpc lists with / without keys LIST='foobarbar' diff --git a/test/test_submodule.sh b/test/test_submodule.sh index a12df67f..0169dbbf 100755 --- a/test/test_submodule.sh +++ b/test/test_submodule.sh @@ -238,31 +238,31 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "main2021-03-08urn:example:clixonAimplementsub1" new "RESTCONF get module state" -expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/data/ietf-yang-library:modules-state/module=main,2021-03-08?config=nonconfig)" 0 'HTTP/1.1 200 OK' "main2021-03-08urn:example:clixonAimplementsub1" +expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/data/ietf-yang-library:modules-state/module=main,2021-03-08?config=nonconfig)" 0 "HTTP/$HVER 200" "main2021-03-08urn:example:clixonAimplementsub1" if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_yang_anydata.sh b/test/test_yang_anydata.sh index 16043e41..a2342341 100755 --- a/test/test_yang_anydata.sh +++ b/test/test_yang_anydata.sh @@ -215,7 +215,7 @@ EOF expectpart "$($clixon_cli -1 -f $cfg commit)" 0 "^$" new "restconf get config" - expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/data?content=config)" 0 "HTTP/1.1 200 OK" "$XML" + expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/data?content=config)" 0 "HTTP/$HVER 200" "$XML" # Save partial state in state file with unknown removed (positive test) echo "$STATE1" > $fstate @@ -224,7 +224,7 @@ EOF expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^$STATE1]]>]]>" new "restconf get state(positive)" - expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/data?content=nonconfig)" 0 "HTTP/1.1 200 OK" "$STATE1" + expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/data?content=nonconfig)" 0 "HTTP/$HVER 200" "$STATE1" # full state with unknowns echo "$STATE0" > $fstate @@ -233,7 +233,7 @@ EOF expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "error-message>Failed to find YANG spec of XML node: u5 with parent: sb in namespace: urn:example:unknown. Internal error, state callback returned invalid XML from plugin: example_backend" new "restconf get state(negative)" - expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/data?content=nonconfig)" 0 "HTTP/1.1 412 Precondition Failed" "operation-failedu5" + expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/data?content=nonconfig)" 0 "HTTP/$HVER 412" "operation-failedu5" # RPC:s take "not-supported" as OK: syntax OK and according to mdeol, just not implemented in # server. But "unknown-element" as truly unknwon. diff --git a/test/test_yang_namespace.sh b/test/test_yang_namespace.sh index 5f59d525..39aa0115 100755 --- a/test/test_yang_namespace.sh +++ b/test/test_yang_namespace.sh @@ -104,21 +104,21 @@ new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" new "restconf set x in example1" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"example1:x":42}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"example1:x":42}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 201" new "restconf get config example1" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example1:x)" 0 "HTTP/1.1 200 OK" '{"example1:x":42}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example1:x)" 0 "HTTP/$HVER 200" '{"example1:x":42}' new "restconf set x in example2" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"example2:x":{"y":99}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"example2:x":{"y":99}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 201" # XXX GET ../example1:x is translated to select=/x which gets both example1&2 #new "restconf get config example1" -#expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example1:x)" 0 "HTTP/1.1 200 OK" '{"example1:x":42}' +#expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example1:x)" 0 "HTTP/$HVER 200" '{"example1:x":42}' # XXX GET ../example2:x is translated to select=/x which gets both example1&2 #new "restconf get config example2" -#expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example2:x)" 0 "HTTP/1.1 200 OK" '{"example2:x":{"y":42}}' +#expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example2:x)" 0 "HTTP/$HVER 200" '{"example2:x":{"y":42}}' new "restconf get config example1 and example2" ret=$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data)