* Restconf FCGI (eg via nginx) have changed reply message syntax slightly

* native http: new restconf_err files, generic data input, restconf_methods generalized.
* test: expecteq removed.
This commit is contained in:
Olof hagsand 2020-06-16 21:51:00 +02:00
parent c18c40434f
commit 6e714beea5
37 changed files with 1619 additions and 1128 deletions

View file

@ -32,7 +32,7 @@ Expected: July 2020
* New mode `GT_HIDE` set by option `CLICON_CLI_GENMODEL_TYPE` to collapse non-presence containers that only contain a single list * New mode `GT_HIDE` set by option `CLICON_CLI_GENMODEL_TYPE` to collapse non-presence containers that only contain a single list
* Added a prefix for cli_show_config/cli_show_auto so that it can produce parseable output * Added a prefix for cli_show_config/cli_show_auto so that it can produce parseable output
* Thanks dcornejo@netgate.com for trying it out and suggestions * Thanks dcornejo@netgate.com for trying it out and suggestions
* Embedding restconf into the existing [libevhtp](https://github.com/criticalstack/libevhtp) embedded web server. Experimental. * Embedding restconf into the existing [libevhtp](https://github.com/criticalstack/libevhtp) embedded web server. (Experimental).
* The existing FCGI restconf solution will continue to be supported for NGINX and other reverese proxies with an fast CGI API. * The existing FCGI restconf solution will continue to be supported for NGINX and other reverese proxies with an fast CGI API.
* The restconf code has been refactored to support both modes. Hopefully, it should be straightforward to add another embedded server, such as GNU microhttpd. * The restconf code has been refactored to support both modes. Hopefully, it should be straightforward to add another embedded server, such as GNU microhttpd.
* The new restconf module is selected using a compile-time autotools configure as follows: * The new restconf module is selected using a compile-time autotools configure as follows:
@ -40,6 +40,12 @@ Expected: July 2020
* `--with-restconf=evhtp Integrate restconf with libevhtp server` * `--with-restconf=evhtp Integrate restconf with libevhtp server`
* `--without-restconf Disable restconf altogether` * `--without-restconf Disable restconf altogether`
### API changes on existing protocol/config features (For users)
* Restconf FCGI (eg via nginx) have changed reply message syntax as follows (due to refactoring and common code with evhtp):
* Bodies in error reyruns including html code have been removed
* Some (extra) CRLF:s have been removed
### C/CLI-API changes on existing features (For developers) ### C/CLI-API changes on existing features (For developers)
* Added prefix for cli_show_config/cli_show_auto so that it can produce parseable output * Added prefix for cli_show_config/cli_show_auto so that it can produce parseable output

View file

@ -85,15 +85,15 @@ APPL = clixon_restconf
APPSRC = APPSRC =
APPSRC += restconf_api.c # maybe empty APPSRC += restconf_api.c # maybe empty
APPSRC += restconf_api_$(with_restconf).c # cant be .so since libevhtp is a. APPSRC += restconf_api_$(with_restconf).c # cant be .so since libevhtp is a.
APPSRC += restconf_err.c
APPSRC += restconf_root.c APPSRC += restconf_root.c
APPSRC += restconf_$(with_restconf)_main.c APPSRC += restconf_$(with_restconf)_main.c
# Fcgi-specific source including main
ifeq ($(with_restconf),fcgi)
APPSRC += restconf_fcgi_lib.c # Most of these should be made generic
APPSRC += restconf_methods.c APPSRC += restconf_methods.c
APPSRC += restconf_methods_post.c APPSRC += restconf_methods_post.c
APPSRC += restconf_methods_get.c APPSRC += restconf_methods_get.c
# Fcgi-specific source including main
ifeq ($(with_restconf),fcgi)
APPSRC += restconf_stream.c APPSRC += restconf_stream.c
endif endif

View file

@ -41,14 +41,14 @@
/* /*
* Prototypes * Prototypes
*/ */
int restconf_reply_status_code(void *req, int code);
#if defined(__GNUC__) && __GNUC__ >= 3 #if defined(__GNUC__) && __GNUC__ >= 3
int restconf_reply_header_add(void *req, char *name, char *vfmt, ...) __attribute__ ((format (printf, 3, 4))); int restconf_reply_header(void *req, char *name, char *vfmt, ...) __attribute__ ((format (printf, 3, 4)));
#else #else
int restconf_reply_header_add(FCGX_Request *req, char *name, char *vfmt, ...); int restconf_reply_header(FCGX_Request *req, char *name, char *vfmt, ...);
#endif #endif
int restconf_reply_send(void *req, cbuf *cb); int restconf_reply_send(void *req, int code, cbuf *cb);
cbuf *restconf_get_indata(void *req);
#endif /* _RESTCONF_API_H_ */ #endif /* _RESTCONF_API_H_ */

View file

@ -65,22 +65,6 @@
#include "restconf_lib.h" #include "restconf_lib.h"
#include "restconf_api.h" /* Virtual api */ #include "restconf_api.h" /* Virtual api */
/*! Add HTTP header field name and value to reply, evhtp specific
* @param[in] req Evhtp http request handle
* @param[in] code HTTP status code
* @see eg RFC 7230
*/
int
restconf_reply_status_code(void *req0,
int code)
{
evhtp_request_t *req = (evhtp_request_t *)req0;
req->status = code;
return 0;
}
/*! Add HTTP header field name and value to reply, evhtp specific /*! Add HTTP header field name and value to reply, evhtp specific
* @param[in] req Evhtp http request handle * @param[in] req Evhtp http request handle
* @param[in] name HTTP header field name * @param[in] name HTTP header field name
@ -88,7 +72,7 @@ restconf_reply_status_code(void *req0,
* @see eg RFC 7230 * @see eg RFC 7230
*/ */
int int
restconf_reply_header_add(void *req0, restconf_reply_header(void *req0,
char *name, char *name,
char *vfmt, char *vfmt,
...) ...)
@ -142,13 +126,20 @@ restconf_reply_header_add(void *req0,
*/ */
int int
restconf_reply_send(void *req0, restconf_reply_send(void *req0,
int code,
cbuf *cb) cbuf *cb)
{ {
evhtp_request_t *req = (evhtp_request_t *)req0; evhtp_request_t *req = (evhtp_request_t *)req0;
int retval = -1; int retval = -1;
evhtp_connection_t *conn; evhtp_connection_t *conn;
struct evbuffer *eb = NULL; struct evbuffer *eb = NULL;
const char *reason_phrase;
req->status = code;
if ((reason_phrase = restconf_code2reason(code)) == NULL)
reason_phrase="";
if (restconf_reply_header(req, "Status", "%d %s", code, reason_phrase) < 0)
goto done;
#if 1 /* Optional? */ #if 1 /* Optional? */
if ((conn = evhtp_request_get_connection(req)) == NULL){ if ((conn = evhtp_request_get_connection(req)) == NULL){
clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection"); clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection");
@ -181,3 +172,20 @@ restconf_reply_send(void *req0,
evhtp_safe_free(eb, evbuffer_free); evhtp_safe_free(eb, evbuffer_free);
return retval; return retval;
} }
/*! get input data
* @param[in] req Fastcgi request handle
* @note Pulls up an event buffer and then copies it to a cbuf. This is not efficient.
*/
cbuf *
restconf_get_indata(void *req0)
{
evhtp_request_t *req = (evhtp_request_t *)req0;
cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL)
return NULL;
if (evbuffer_get_length(req->buffer_in))
cprintf(cb, "%s", evbuffer_pullup(req->buffer_in, -1));
return cb;
}

View file

@ -63,20 +63,6 @@
#include "restconf_lib.h" #include "restconf_lib.h"
#include "restconf_api.h" /* Virtual api */ #include "restconf_api.h" /* Virtual api */
/*! Add HTTP header field name and value to reply, fcgi specific
* @param[in] req Fastcgi request handle
* @param[in] code HTTP status code
* @see eg RFC 7230
*/
int
restconf_reply_status_code(void *req0,
int code)
{
FCGX_Request *req = (FCGX_Request *)req0;
FCGX_SetExitStatus(code, req->out);
return 0;
}
/*! HTTP headers done, if there is a message body coming next /*! HTTP headers done, if there is a message body coming next
* @param[in] req Fastcgi request handle * @param[in] req Fastcgi request handle
@ -102,11 +88,10 @@ restconf_reply_body_start(void *req0)
* @see eg RFC 7230 * @see eg RFC 7230
*/ */
int int
restconf_reply_header_add(void *req0, restconf_reply_header(void *req0,
char *name, char *name,
char *vfmt, char *vfmt,
...) ...)
{ {
FCGX_Request *req = (FCGX_Request *)req0; FCGX_Request *req = (FCGX_Request *)req0;
int retval = -1; int retval = -1;
@ -196,22 +181,49 @@ restconf_reply_body_add(void *req0,
/*! Send HTTP reply with potential message body /*! Send HTTP reply with potential message body
* @param[in] req Fastcgi request handle * @param[in] req Fastcgi request handle
* @param[in] cb Body as a cbuf, send if * @param[in] code Status code
* @param[in] cb Body as a cbuf if non-NULL
* *
* Prerequisites: status code set, headers given, body if wanted set * Prerequisites: status code set, headers given, body if wanted set
*/ */
int int
restconf_reply_send(void *req0, restconf_reply_send(void *req0,
int code,
cbuf *cb) cbuf *cb)
{ {
FCGX_Request *req = (FCGX_Request *)req0; FCGX_Request *req = (FCGX_Request *)req0;
int retval = -1; int retval = -1;
const char *reason_phrase;
FCGX_SetExitStatus(code, req->out);
if ((reason_phrase = restconf_code2reason(code)) == NULL)
reason_phrase="";
if (restconf_reply_header(req, "Status", "%d %s", code, reason_phrase) < 0)
goto done;
FCGX_FPrintF(req->out, "\r\n");
/* Write a body if cbuf is nonzero */ /* Write a body if cbuf is nonzero */
if (cb != NULL && cbuf_len(cb)){ if (cb != NULL && cbuf_len(cb)){
FCGX_FPrintF(req->out, "\r\n");
FCGX_FPrintF(req->out, "%s", cbuf_get(cb)); FCGX_FPrintF(req->out, "%s", cbuf_get(cb));
FCGX_FPrintF(req->out, "\r\n");
} }
retval = 0; retval = 0;
done:
return retval; return retval;
} }
/*!
* @param[in] req Fastcgi request handle
*/
cbuf *
restconf_get_indata(void *req0)
{
FCGX_Request *req = (FCGX_Request *)req0;
int c;
cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL)
return NULL;
while ((c = FCGX_GetChar(req->in)) != -1)
cprintf(cb, "%c", c);
return cb;
}

View file

@ -0,0 +1,495 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2020 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
*
* Return errors
* @see RFC 7231 Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content
*/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <ctype.h>
#include <time.h>
#include <signal.h>
#include <dlfcn.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/wait.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include "restconf_lib.h"
#include "restconf_api.h"
#include "restconf_err.h"
/*
* Constants
*/
/* In the fcgi implementations some errors had body, it would be cleaner to skip them
* None seem mandatory according to RFC 7231
*/
#define SKIP_BODY
/*
* NOTE, fcgi seems not enough with a status code (libevhtp is) but must also have a status
* header.
*/
/*! HTTP error 400
* @param[in] h Clicon handle
* @param[in] req Generic Www handle
*/
int
restconf_badrequest(clicon_handle h,
void *req)
{
int retval = -1;
#ifdef SKIP_BODY /* Remove the body - should it really be there? */
if (restconf_reply_send(req, 400, NULL) < 0)
goto done;
retval = 0;
done:
#else
char *path;
cbuf *cb = NULL;
/* Create body */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
path = clixon_restconf_param_get("REQUEST_URI", r->envp);
if (restconf_reply_header(req, "Content-Type", "text/html") < 0)
goto done;
cprintf(cb, "The requested URL %s or data is in some way badly formed.\n", path);
if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)
goto done;
if (restconf_reply_send(req, 400, cb) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
#endif
return retval;
}
/*! HTTP error 401
* @param[in] h Clicon handle
* @param[in] req Generic Www handle
*/
int
restconf_unauthorized(clicon_handle h,
void *req)
{
int retval = -1;
#ifdef SKIP_BODY /* Remove the body - should it really be there? */
if (restconf_reply_send(req, 400, NULL) < 0)
goto done;
retval = 0;
done:
#else
char *path;
cbuf *cb = NULL;
/* Create body */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
path = clixon_restconf_param_get("REQUEST_URI", r->envp);
if (restconf_reply_header(req, "Content-Type", "text/html") < 0)
goto done;
cprintf(cb, "<error-tag>access-denied</error-tag>\n");
cprintf(cb, "The requested URL %s was unauthorized.\n", path);
if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)
goto done;
if (restconf_reply_send(req, 400, cb) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
#endif
return retval;
}
/*! HTTP error 403
* @param[in] h Clicon handle
* @param[in] req Generic Www handle
*/
int
restconf_forbidden(clicon_handle h,
void *req)
{
int retval = -1;
#ifdef SKIP_BODY /* Remove the body - should it really be there? */
if (restconf_reply_send(req, 403, NULL) < 0)
goto done;
retval = 0;
done:
#else
char *path;
cbuf *cb = NULL;
/* Create body */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
path = clixon_restconf_param_get("REQUEST_URI", r->envp);
if (restconf_reply_header(req, "Content-Type", "text/html") < 0)
goto done;
cprintf(cb, "The requested URL %s was forbidden.\n", path);
if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)
goto done;
if (restconf_reply_send(req, 403, cb) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
#endif
return retval;
}
/*! HTTP error 404
* @param[in] h Clicon handle
* @param[in] req Generic Www handle
* XXX skip body?
*/
int
restconf_notfound(clicon_handle h,
void *req)
{
int retval = -1;
#ifdef SKIP_BODY /* Remove the body - should it really be there? */
if (restconf_reply_send(req, 404, NULL) < 0)
goto done;
retval = 0;
done:
#else
char *path;
cbuf *cb = NULL;
/* Create body */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
path = clixon_restconf_param_get("REQUEST_URI", r->envp);
if (restconf_reply_header(req, "Content-Type", "text/html") < 0)
goto done;
cprintf(cb, "The requested URL %s was not found on this server.\n", path);
if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)
goto done;
if (restconf_reply_send(req, 404, cb) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
#endif
return retval;
}
/*! HTTP error 405
* @param[in] req Generic Www handle
* @param[in] allow Which methods are allowed
*/
int
restconf_method_notallowed(void *req,
char *allow)
{
int retval = -1;
if (restconf_reply_header(req, "Allow", "%s", allow) < 0)
goto done;
if (restconf_reply_send(req, 405, NULL) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! HTTP error 406 Not acceptable
* @param[in] h Clicon handle
* @param[in] req Generic Www handle
*/
int
restconf_notacceptable(clicon_handle h,
void *req)
{
int retval = -1;
#ifdef SKIP_BODY /* Remove the body - should it really be there? */
if (restconf_reply_send(req, 406, NULL) < 0)
goto done;
retval = 0;
done:
#else
char *path;
cbuf *cb = NULL;
/* Create body */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
path = clixon_restconf_param_get("REQUEST_URI", r->envp);
if (restconf_reply_header(req, "Content-Type", "text/html") < 0)
goto done;
cprintf(cb, "The target resource does not have a current representation that would be acceptable to the user agent.\n", path);
if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)
goto done;
if (restconf_reply_send(req, 406, cb) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
#endif
return retval;
}
/*! HTTP error 409
* @param[in] req Generic Www handle
*/
int
restconf_conflict(void *req)
{
int retval = -1;
if (restconf_reply_send(req, 409, NULL) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! HTTP error 409 Unsupporte dmedia
* @param[in] req Generic Www handle
*/
int
restconf_unsupported_media(void *req)
{
int retval = -1;
if (restconf_reply_send(req, 415, NULL) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! HTTP error 500 Internal server error
* @param[in] h Clicon handle
* @param[in] req Generic Www handle
*/
int
restconf_internal_server_error(clicon_handle h,
void *req)
{
int retval = -1;
#ifdef SKIP_BODY /* Remove the body - should it really be there? */
if (restconf_reply_send(req, 500, NULL) < 0)
goto done;
retval = 0;
done:
#else
char *path;
cbuf *cb = NULL;
/* Create body */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
path = clixon_restconf_param_get("REQUEST_URI", r->envp);
if (restconf_reply_header(req, "Content-Type", "text/html") < 0)
goto done;
cprintf(cb, "Internal server error when accessing %s</h1>\n", path);
if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)=
goto done;
if (restconf_reply_send(req, 500, cb) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
#endif
return retval;
}
/*! HTTP error 501 Not implemented
* @param[in] req Generic Www handle
*/
int
restconf_notimplemented(void *req)
{
int retval = -1;
if (restconf_reply_send(req, 501, NULL) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Generic restconf error function on get/head request
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] xerr XML error message from backend
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media Output media
* @param[in] code If 0 use rfc8040 sec 7 netconf2restconf error-tag mapping
* otherwise use this code
*/
int
api_return_err(clicon_handle h,
void *req,
cxobj *xerr,
int pretty,
restconf_media media,
int code0)
{
int retval = -1;
cbuf *cb = NULL;
cbuf *cberr = NULL;
cxobj *xtag;
char *tagstr;
int code;
cxobj *xerr2 = NULL;
clicon_debug(1, "%s", __FUNCTION__);
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* A well-formed error message when entering here should look like:
* <rpc-error>...<error-tag>invalid-value</error-tag>
* Check this is so, otherwise generate an internal error.
*/
if (strcmp(xml_name(xerr), "rpc-error") != 0 ||
(xtag = xpath_first(xerr, NULL, "error-tag")) == NULL){
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cberr, "Internal error, system returned invalid error message: ");
if (netconf_err2cb(xerr, cberr) < 0)
goto done;
if (netconf_operation_failed_xml(&xerr2, "application",
cbuf_get(cberr)) < 0)
goto done;
if ((xerr = xpath_first(xerr2, NULL, "//rpc-error")) == NULL){
clicon_err(OE_XML, 0, "Internal error, shouldnt happen");
goto done;
}
}
if (xml_name_set(xerr, "error") < 0)
goto done;
tagstr = xml_body(xtag);
if (code0 != 0)
code = code0;
else{
if ((code = restconf_err2code(tagstr)) < 0)
code = 500; /* internal server error */
}
if (restconf_reply_header(req, "Content_Type", "%s", restconf_media_int2str(media)) < 0)
goto done;
switch (media){
case YANG_DATA_XML:
clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb));
if (pretty){
cprintf(cb, " <errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">\n");
if (clicon_xml2cbuf(cb, xerr, 2, pretty, -1) < 0)
goto done;
cprintf(cb, " </errors>\r\n");
}
else {
cprintf(cb, "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">");
if (clicon_xml2cbuf(cb, xerr, 2, pretty, -1) < 0)
goto done;
cprintf(cb, "</errors>\r\n");
}
break;
case YANG_DATA_JSON:
clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb));
if (pretty){
cprintf(cb, "{\n\"ietf-restconf:errors\" : ");
if (xml2json_cbuf(cb, xerr, pretty) < 0)
goto done;
cprintf(cb, "\n}\r\n");
}
else{
cprintf(cb, "{");
cprintf(cb, "\"ietf-restconf:errors\":");
if (xml2json_cbuf(cb, xerr, pretty) < 0)
goto done;
cprintf(cb, "}\r\n");
}
break;
default:
clicon_err(OE_YANG, EINVAL, "Invalid media type %d", media);
goto done;
break;
} /* switch media */
if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)
goto done;
if (restconf_reply_send(req, code, cb) < 0)
goto done;
// ok:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cb)
cbuf_free(cb);
if (cberr)
cbuf_free(cberr);
return retval;
}

View file

@ -31,30 +31,30 @@
the terms of any one of the Apache License version 2 or the GPL. the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
*
* Return errors
* @see RFC 7231 Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content
*/ */
#ifndef _RESTCONF_FCGI_LIB_H_ #ifndef _RESTCONF_ERR_H_
#define _RESTCONF_FCGI_LIB_H_ #define _RESTCONF_ERR_H_
/* /*
* Prototypes * Prototypes
*/ */
int restconf_badrequest(clicon_handle h, FCGX_Request *r);
int restconf_unauthorized(clicon_handle h, FCGX_Request *r);
int restconf_forbidden(clicon_handle h, FCGX_Request *r);
int restconf_notfound(clicon_handle h, FCGX_Request *r);
int restconf_notacceptable(clicon_handle h, FCGX_Request *r);
int restconf_conflict(FCGX_Request *r);
int restconf_unsupported_media(FCGX_Request *r);
int restconf_internal_server_error(clicon_handle h, FCGX_Request *r);
int restconf_notimplemented(FCGX_Request *r);
int restconf_test(FCGX_Request *r, int dbg);
int clixon_restconf_params_set(clicon_handle h,
char **envp);
int clixon_restconf_params_clear(clicon_handle h, char **envp);
cbuf *readdata(FCGX_Request *r);
int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr, int pretty, restconf_media media, int code0);
int http_location(clicon_handle h, FCGX_Request *r, cxobj *xobj);
#endif /* _RESTCONF_FCGI_LIB_H_ */ int restconf_badrequest(clicon_handle h, void *req);
int restconf_unauthorized(clicon_handle h, void *req);
int restconf_forbidden(clicon_handle h, void *req);
int restconf_notfound(clicon_handle h, void *req);
int restconf_method_notallowed(void *req, char *allow);
int restconf_notacceptable(clicon_handle h, void *req);
int restconf_conflict(void *req);
int restconf_unsupported_media(void *req);
int restconf_internal_server_error(clicon_handle h, void *req);
int restconf_notimplemented(void *req);
int api_return_err(clicon_handle h, void *req, cxobj *xerr, int pretty, restconf_media media, int code0);
#endif /* _RESTCONF_ERR_H_ */

View file

@ -178,6 +178,7 @@ query_iterator(evhtp_header_t *hdr,
* several different places in evhtp * several different places in evhtp
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] req Evhtp request struct * @param[in] req Evhtp request struct
* @param[out] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* The following parameters are set: * The following parameters are set:
@ -211,7 +212,10 @@ evhtp_params_set(clicon_handle h,
} }
meth = evhtp_request_get_method(req); meth = evhtp_request_get_method(req);
/* QUERY_STRING */ /* QUERY_STRING in fcgi but go direct to the info instead of putting it in a string?
* This is different from all else: Ie one could have re-created a string here but
* that would mean double parsing,...
*/
if (qvec && uri->query) if (qvec && uri->query)
if (evhtp_kvs_for_each(uri->query, query_iterator, qvec) < 0){ if (evhtp_kvs_for_each(uri->query, query_iterator, qvec) < 0){
clicon_err(OE_CFG, errno, "evhtp_kvs_for_each"); clicon_err(OE_CFG, errno, "evhtp_kvs_for_each");
@ -349,70 +353,29 @@ static void
cx_path_restconf(evhtp_request_t *req, cx_path_restconf(evhtp_request_t *req,
void *arg) void *arg)
{ {
evhtp_connection_t *conn;
clicon_handle h = arg; clicon_handle h = arg;
struct evbuffer *b = NULL;
cvec *qvec = NULL; cvec *qvec = NULL;
size_t len = 0;
cbuf *cblen = NULL;
clicon_debug(1, "%s", __FUNCTION__);
if (req == NULL){
errno = EINVAL;
goto done;
}
/* input debug */ /* input debug */
if (clicon_debug_get()) if (clicon_debug_get())
evhtp_headers_for_each(req->headers_in, print_header, h); evhtp_headers_for_each(req->headers_in, print_header, h);
/* get accepted connection */
if ((cblen = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* Query vector, ie the ?a=x&b=y stuff */ /* Query vector, ie the ?a=x&b=y stuff */
if ((qvec = cvec_new(0)) ==NULL){ if ((qvec = cvec_new(0)) ==NULL){
clicon_err(OE_UNIX, errno, "cvec_new"); clicon_err(OE_UNIX, errno, "cvec_new");
goto done; goto done;
} }
/* get accepted connection */ /* set fcgi-like paramaters (ignore query vector) */
if ((conn = evhtp_request_get_connection(req)) == NULL){
clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection");
goto done;
}
/* Get all parameters from this request (resembling fcgi) */
if (evhtp_params_set(h, req, qvec) < 0) if (evhtp_params_set(h, req, qvec) < 0)
goto done; goto done;
/* call generic function */
/* 1. create body */ if (api_root_restconf(h, req, qvec) < 0)
if ((b = evbuffer_new()) == NULL){
clicon_err(OE_DAEMON, errno, "evbuffer_new");
goto done; goto done;
}
cprintf(cblen, "%lu", len);
/* 2. add headers (can mix with body) */ /* Clear (fcgi) paramaters from this request */
evhtp_headers_add_header(req->headers_out, evhtp_header_new("Cache-Control", "no-cache", 0, 0));
evhtp_headers_add_header(req->headers_out, evhtp_header_new("Content-Type", "application/xrd+xml", 0, 0));
evhtp_headers_add_header(req->headers_out, evhtp_header_new("Content-Length", cbuf_get(cblen), 0, 0));
/* Optional? */
htp_sslutil_add_xheaders(req->headers_out, conn->ssl, HTP_SSLUTILS_XHDR_ALL);
/* 3. send reply */
evhtp_send_reply_start(req, EVHTP_RES_OK);
evhtp_send_reply_body(req, b);
evhtp_send_reply_end(req);
/* Clear (fcgi)paramaters */
if (evhtp_params_clear(h) < 0) if (evhtp_params_clear(h) < 0)
goto done; goto done;
done: done:
if (qvec)
cvec_free(qvec);
if (cblen)
cbuf_free(cblen);
if (b)
evhtp_safe_free(b, evbuffer_free);
return; /* void */ return; /* void */
} }
@ -756,7 +719,6 @@ main(int argc,
if (clicon_options_main(h) < 0) if (clicon_options_main(h) < 0)
goto done; goto done;
event_base_loop(evbase, 0); event_base_loop(evbase, 0);
evhtp_unbind_socket(htp); evhtp_unbind_socket(htp);

View file

@ -1,480 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2020 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
* @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
* @note The response payload for errors uses text_html. RFC7231 is vague
* on the response payload (and its media). Maybe it should be omitted
* altogether?
*/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <ctype.h>
#include <time.h>
#include <signal.h>
#include <dlfcn.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/wait.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include <fcgiapp.h> /* Need to be after clixon_xml-h due to attribute format */
#include "restconf_lib.h"
#include "restconf_fcgi_lib.h"
/*! HTTP error 400
* @param[in] req Fastcgi request handle
*/
int
restconf_badrequest(clicon_handle h,
FCGX_Request *req)
{
char *path;
path = clixon_restconf_param_get(h, "REQUEST_URI");
FCGX_SetExitStatus(400, req->out);
FCGX_FPrintF(req->out, "Status: 400 Bad Request\r\n"); /* 400 bad request */
FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(req->out, "<h1>Clixon Bad request/h1>\n");
FCGX_FPrintF(req->out, "The requested URL %s or data is in some way badly formed.\n",
path);
return 0;
}
/*! HTTP error 401
* @param[in] req Fastcgi request handle
*/
int
restconf_unauthorized(clicon_handle h,
FCGX_Request *req)
{
char *path;
path = clixon_restconf_param_get(h, "REQUEST_URI");
FCGX_SetExitStatus(401, req->out);
FCGX_FPrintF(req->out, "Status: 401 Unauthorized\r\n"); /* 401 unauthorized */
FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(req->out, "<error-tag>access-denied</error-tag>\n");
FCGX_FPrintF(req->out, "The requested URL %s was unauthorized.\n", path);
return 0;
}
/*! HTTP error 403
* @param[in] req Fastcgi request handle
*/
int
restconf_forbidden(clicon_handle h,
FCGX_Request *req)
{
char *path;
path = clixon_restconf_param_get(h, "REQUEST_URI");
FCGX_SetExitStatus(403, req->out);
FCGX_FPrintF(req->out, "Status: 403 Forbidden\r\n"); /* 403 forbidden */
FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(req->out, "<h1>Forbidden</h1>\n");
FCGX_FPrintF(req->out, "The requested URL %s was forbidden.\n", path);
return 0;
}
/*! HTTP error 404
* @param[in] req Fastcgi request handle
*/
int
restconf_notfound(clicon_handle h,
FCGX_Request *req)
{
char *path;
path = clixon_restconf_param_get(h, "REQUEST_URI");
FCGX_SetExitStatus(404, req->out);
FCGX_FPrintF(req->out, "Status: 404 Not Found\r\n"); /* 404 not found */
FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(req->out, "<h1>Not Found</h1>\n");
FCGX_FPrintF(req->out, "Not Found\n");
FCGX_FPrintF(req->out, "The requested URL %s was not found on this server.\n",
path);
return 0;
}
/*! HTTP error 406 Not acceptable
* @param[in] req Fastcgi request handle
*/
int
restconf_notacceptable(clicon_handle h,
FCGX_Request *req)
{
char *path;
path = clixon_restconf_param_get(h, "REQUEST_URI");
FCGX_SetExitStatus(406, req->out);
FCGX_FPrintF(req->out, "Status: 406 Not Acceptable\r\n"); /* 406 not acceptible */
FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(req->out, "<h1>Not Acceptable</h1>\n");
FCGX_FPrintF(req->out, "Not Acceptable\n");
FCGX_FPrintF(req->out, "The target resource does not have a current representation that would be acceptable to the user agent.\n",
path);
return 0;
}
/*! HTTP error 409
* @param[in] req Fastcgi request handle
*/
int
restconf_conflict(FCGX_Request *req)
{
FCGX_SetExitStatus(409, req->out);
FCGX_FPrintF(req->out, "Status: 409 Conflict\r\n"); /* 409 Conflict */
FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(req->out, "<h1>Data resource already exists</h1>\n");
return 0;
}
/*! HTTP error 409
* @param[in] req Fastcgi request handle
*/
int
restconf_unsupported_media(FCGX_Request *req)
{
FCGX_SetExitStatus(415, req->out);
FCGX_FPrintF(req->out, "Status: 415 Unsupported Media Type\r\n");
FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(req->out, "<h1>Unsupported Media Type</h1>\n");
return 0;
}
/*! HTTP error 500
* @param[in] req Fastcgi request handle
*/
int
restconf_internal_server_error(clicon_handle h,
FCGX_Request *req)
{
char *path;
clicon_debug(1, "%s", __FUNCTION__);
path = clixon_restconf_param_get(h, "REQUEST_URI");
FCGX_FPrintF(req->out, "Status: 500 Internal Server Error\r\n"); /* 500 internal server error */
FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(req->out, "<h1>Internal server error when accessing %s</h1>\n", path);
return 0;
}
/*! HTTP error 501
* @param[in] req Fastcgi request handle
*/
int
restconf_notimplemented(FCGX_Request *req)
{
clicon_debug(1, "%s", __FUNCTION__);
FCGX_FPrintF(req->out, "Status: 501 Not Implemented\r\n");
FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(req->out, "<h1>Not Implemented/h1>\n");
return 0;
}
/*! Print all FCGI headers
* @param[in] req Fastcgi request handle
* @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
*/
int
restconf_test(FCGX_Request *req,
int dbg)
{
char **environ = req->envp;
int i;
clicon_debug(1, "All environment vars:");
for (i = 0; environ[i] != NULL; i++){
clicon_debug(1, "%s", environ[i]);
}
clicon_debug(1, "End environment vars");
return 0;
}
/*! Convert FCGI parameters to clixon runtime data
* @param[in] h Clixon handle
* @param[in] envp Fastcgi request handle parameter array on the format "<param>=<value>"
* @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
*/
int
clixon_restconf_params_set(clicon_handle h,
char **envp)
{
int retval = -1;
int i;
char *param = NULL;
char *val = NULL;
clicon_debug(1, "%s", __FUNCTION__);
for (i = 0; envp[i] != NULL; i++){ /* on the form <param>=<value> */
if (clixon_strsplit(envp[i], '=', &param, &val) < 0)
goto done;
clicon_debug(1, "%s param:%s val:%s", __FUNCTION__, param, val);
if (clixon_restconf_param_set(h, param, val) < 0)
goto done;
if (param){
free(param);
param = NULL;
}
if (val){
free(val);
val = NULL;
}
}
retval = 0;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
return retval;
}
/*! Clear all FCGI parameters in an environment
* @param[in] h Clixon handle
* @param[in] envp Fastcgi request handle parameter array on the format "<param>=<value>"
* @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
*/
int
clixon_restconf_params_clear(clicon_handle h,
char **envp)
{
int retval = -1;
int i;
char *param = NULL;
clicon_debug(1, "%s", __FUNCTION__);
for (i = 0; envp[i] != NULL; i++){ /* on the form <param>=<value> */
if (clixon_strsplit(envp[i], '=', &param, NULL) < 0)
goto done;
clicon_debug(1, "%s param:%s", __FUNCTION__, param);
if (clixon_restconf_param_del(h, param) < 0)
goto done;
if (param){
free(param);
param = NULL;
}
}
retval = 0;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
return retval;
}
/*!
* @param[in] req Fastcgi request handle
*/
cbuf *
readdata(FCGX_Request *req)
{
int c;
cbuf *cb;
if ((cb = cbuf_new()) == NULL)
return NULL;
while ((c = FCGX_GetChar(req->in)) != -1)
cprintf(cb, "%c", c);
return cb;
}
/*! Return restconf error on get/head request
* @param[in] h Clixon handle
* @param[in] req Fastcgi request handle
* @param[in] xerr XML error message from backend
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media Output media
* @param[in] code If 0 use rfc8040 sec 7 netconf2restconf error-tag mapping
* otherwise use this code
*/
int
api_return_err(clicon_handle h,
FCGX_Request *req,
cxobj *xerr,
int pretty,
restconf_media media,
int code0)
{
int retval = -1;
cbuf *cb = NULL;
cbuf *cberr = NULL;
cxobj *xtag;
char *tagstr;
int code;
cxobj *xerr2 = NULL;
const char *reason_phrase;
clicon_debug(1, "%s", __FUNCTION__);
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* A well-formed error message when entering here should look like:
* <rpc-error>...<error-tag>invalid-value</error-tag>
* Check this is so, otherwise generate an internal error.
*/
if (strcmp(xml_name(xerr), "rpc-error") != 0 ||
(xtag = xpath_first(xerr, NULL, "error-tag")) == NULL){
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cberr, "Internal error, system returned invalid error message: ");
if (netconf_err2cb(xerr, cberr) < 0)
goto done;
if (netconf_operation_failed_xml(&xerr2, "application",
cbuf_get(cberr)) < 0)
goto done;
if ((xerr = xpath_first(xerr2, NULL, "//rpc-error")) == NULL){
clicon_err(OE_XML, 0, "Internal error, shouldnt happen");
goto done;
}
}
if (xml_name_set(xerr, "error") < 0)
goto done;
tagstr = xml_body(xtag);
if (code0 != 0)
code = code0;
else{
if ((code = restconf_err2code(tagstr)) < 0)
code = 500; /* internal server error */
}
if ((reason_phrase = restconf_code2reason(code)) == NULL)
reason_phrase="";
FCGX_SetExitStatus(code, req->out); /* Created */
FCGX_FPrintF(req->out, "Status: %d %s\r\n", code, reason_phrase);
FCGX_FPrintF(req->out, "Content-Type: %s\r\n\r\n", restconf_media_int2str(media));
switch (media){
case YANG_DATA_XML:
if (clicon_xml2cbuf(cb, xerr, 2, pretty, -1) < 0)
goto done;
clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb));
if (pretty){
FCGX_FPrintF(req->out, " <errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">\n", cbuf_get(cb));
FCGX_FPrintF(req->out, "%s", cbuf_get(cb));
FCGX_FPrintF(req->out, " </errors>\r\n");
}
else {
FCGX_FPrintF(req->out, "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">", cbuf_get(cb));
FCGX_FPrintF(req->out, "%s", cbuf_get(cb));
FCGX_FPrintF(req->out, "</errors>\r\n");
}
break;
case YANG_DATA_JSON:
if (xml2json_cbuf(cb, xerr, pretty) < 0)
goto done;
clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb));
if (pretty){
FCGX_FPrintF(req->out, "{\n");
FCGX_FPrintF(req->out, " \"ietf-restconf:errors\" : %s\n",
cbuf_get(cb));
FCGX_FPrintF(req->out, "}\r\n");
}
else{
FCGX_FPrintF(req->out, "{");
FCGX_FPrintF(req->out, "\"ietf-restconf:errors\":");
FCGX_FPrintF(req->out, "%s", cbuf_get(cb));
FCGX_FPrintF(req->out, "}\r\n");
}
break;
default:
clicon_err(OE_YANG, EINVAL, "Invalid media type %d", media);
goto done;
break;
} /* switch media */
// ok:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cb)
cbuf_free(cb);
if (cberr)
cbuf_free(cberr);
return retval;
}
/*! Print location header from FCGI environment
* @param[in] req Fastcgi request handle
* @param[in] xobj If set (eg POST) add to api-path
* $https on if connection operates in SSL mode, or an empty string otherwise
* @note ports are ignored
*/
int
http_location(clicon_handle h,
FCGX_Request *req,
cxobj *xobj)
{
int retval = -1;
char *https;
char *host;
char *request_uri;
cbuf *cb = NULL;
https = clixon_restconf_param_get(h, "HTTPS");
host = clixon_restconf_param_get(h, "HTTP_HOST");
request_uri = clixon_restconf_param_get(h, "REQUEST_URI");
if (xobj != NULL){
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, 0, "cbuf_new");
goto done;
}
if (xml2api_path_1(xobj, cb) < 0)
goto done;
FCGX_FPrintF(req->out, "Location: http%s://%s%s%s\r\n",
https?"s":"",
host,
request_uri,
cbuf_get(cb));
}
else
FCGX_FPrintF(req->out, "Location: http%s://%s%s\r\n",
https?"s":"",
host,
request_uri);
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}

View file

@ -79,9 +79,8 @@
/* restconf */ /* restconf */
#include "restconf_lib.h" /* generic shared with plugins */ #include "restconf_lib.h" /* generic shared with plugins */
#include "restconf_api.h" /* generic not shared with plugins */ #include "restconf_api.h" /* generic not shared with plugins */
#include "restconf_err.h"
#include "restconf_root.h" /* generic not shared with plugins */ #include "restconf_root.h" /* generic not shared with plugins */
#include "restconf_fcgi_lib.h" /* fcgi specific */
#include "restconf_methods.h" /* fcgi specific */ #include "restconf_methods.h" /* fcgi specific */
#include "restconf_methods_get.h" #include "restconf_methods_get.h"
#include "restconf_methods_post.h" #include "restconf_methods_post.h"
@ -90,6 +89,90 @@
/* Command line options to be passed to getopt(3) */ /* Command line options to be passed to getopt(3) */
#define RESTCONF_OPTS "hD:f:l:p:d:y:a:u:o:" #define RESTCONF_OPTS "hD:f:l:p:d:y:a:u:o:"
/*! Convert FCGI parameters to clixon runtime data
* @param[in] h Clixon handle
* @param[in] envp Fastcgi request handle parameter array on the format "<param>=<value>"
* @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
*/
static int
fcgi_params_set(clicon_handle h,
char **envp)
{
int retval = -1;
int i;
char *param = NULL;
char *val = NULL;
clicon_debug(1, "%s", __FUNCTION__);
for (i = 0; envp[i] != NULL; i++){ /* on the form <param>=<value> */
if (clixon_strsplit(envp[i], '=', &param, &val) < 0)
goto done;
clicon_debug(1, "%s param:%s val:%s", __FUNCTION__, param, val);
if (clixon_restconf_param_set(h, param, val) < 0)
goto done;
if (param){
free(param);
param = NULL;
}
if (val){
free(val);
val = NULL;
}
}
retval = 0;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
return retval;
}
/*! Clear all FCGI parameters in an environment
* @param[in] h Clixon handle
* @param[in] envp Fastcgi request handle parameter array on the format "<param>=<value>"
* @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
*/
static int
fcgi_params_clear(clicon_handle h,
char **envp)
{
int retval = -1;
int i;
char *param = NULL;
clicon_debug(1, "%s", __FUNCTION__);
for (i = 0; envp[i] != NULL; i++){ /* on the form <param>=<value> */
if (clixon_strsplit(envp[i], '=', &param, NULL) < 0)
goto done;
clicon_debug(1, "%s param:%s", __FUNCTION__, param);
if (clixon_restconf_param_del(h, param) < 0)
goto done;
if (param){
free(param);
param = NULL;
}
}
retval = 0;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
return retval;
}
/*! Print all FCGI headers
* @param[in] req Fastcgi request handle
* @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
*/
static int
fcgi_print_headers(FCGX_Request *req)
{
char **environ = req->envp;
int i;
clicon_debug(1, "All environment vars:");
for (i = 0; environ[i] != NULL; i++)
clicon_debug(1, "%s", environ[i]);
clicon_debug(1, "End environment vars");
return 0;
}
/*! Generic REST method, GET, PUT, DELETE, etc /*! Generic REST method, GET, PUT, DELETE, etc
* @param[in] h CLIXON handle * @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle * @param[in] r Fastcgi request handle
@ -97,7 +180,6 @@
* @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec * @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] dvec Stream input daat
* @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_in Input media * @param[in] media_in Input media
* @param[in] media_out Output media * @param[in] media_out Output media
@ -176,46 +258,6 @@ api_operations(clicon_handle h,
return retval; return retval;
} }
/*! Determine the root of the RESTCONF API
* @param[in] h Clicon handle
* @param[in] r Fastcgi request handle
* @note Hardcoded to "/restconf"
* Return see RFC8040 3.1 and RFC7320
* In line with the best practices defined by [RFC7320], RESTCONF
* enables deployments to specify where the RESTCONF API is located.
*/
#if 0
static int
api_well_known(clicon_handle h,
FCGX_Request *req)
{
char *request_method;
FCGX_Request *body;
/* call generic function */
if (api_well_known(h, req) < 0)
goto done;
clicon_debug(1, "%s", __FUNCTION__);
if (req == NULL){
errno = EINVAL;
goto done;
}
request_method = clixon_restconf_param_get(h, "REQUEST_METHOD");
if (strcmp(request_method, "GET") !=0 )
return restconf_method_notallowed(req, "GET");
restconf_reply_status_code(req, 200); /* OK */
restconf_reply_header_add(req, "Cache-Control", "no-cache");
restconf_reply_header_add(req, "Content-Type", "application/xrd+xml");
body = restconf_reply_body_start(req);
restconf_reply_body_add(body, NULL, "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>\n");
restconf_reply_body_add(body, NULL, " <Link rel='restconf' href='/restconf'/>\n");
restconf_reply_body_add(body, NULL, "</XRD>\r\n");
done:
return 0;
}
#endif
/*! Retrieve the Top-Level API Resource /*! Retrieve the Top-Level API Resource
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] r Fastcgi request handle * @param[in] r Fastcgi request handle
@ -224,7 +266,7 @@ api_well_known(clicon_handle h,
* XXX doesnt check method * XXX doesnt check method
*/ */
static int static int
api_root(clicon_handle h, api_fcgi_root(clicon_handle h,
FCGX_Request *r, FCGX_Request *r,
int pretty, int pretty,
restconf_media media_out) restconf_media media_out)
@ -337,25 +379,23 @@ api_yang_library_version(clicon_handle h,
* @param[in] r Fastcgi request handle * @param[in] r Fastcgi request handle
*/ */
static int static int
api_restconf(clicon_handle h, api_fcgi_restconf(clicon_handle h,
FCGX_Request *req) FCGX_Request *req)
{ {
int retval = -1; int retval = -1;
char *path; char *path;
char *query = NULL; char *query = NULL;
char *method; char *api_resource;
char **pvec = NULL; char **pvec = NULL;
int pn; int pn;
cvec *qvec = NULL; cvec *qvec = NULL;
cvec *dvec = NULL;
cvec *pcvec = NULL; /* for rest api */ cvec *pcvec = NULL; /* for rest api */
cbuf *cb = NULL; cbuf *cb = NULL;
char *data; char *indata = NULL;
int authenticated = 0; int authenticated = 0;
char *media_str = NULL; char *media_str = NULL;
restconf_media media_out = YANG_DATA_JSON; restconf_media media_out = YANG_DATA_JSON;
int pretty; int pretty;
cbuf *cbret = NULL;
cxobj *xret = NULL; cxobj *xret = NULL;
cxobj *xerr; cxobj *xerr;
@ -398,30 +438,28 @@ api_restconf(clicon_handle h,
retval = restconf_notfound(h, req); retval = restconf_notfound(h, req);
goto done; goto done;
} }
restconf_test(req, 1); fcgi_print_headers(req);
if (pn == 2){ if (pn == 2){
retval = api_root(h, req, pretty, media_out); retval = api_fcgi_root(h, req, pretty, media_out);
goto done; goto done;
} }
if ((method = pvec[2]) == NULL){ if ((api_resource = pvec[2]) == NULL){
retval = restconf_notfound(h, req); retval = restconf_notfound(h, req);
goto done; goto done;
} }
clicon_debug(1, "%s: method=%s", __FUNCTION__, method); clicon_debug(1, "%s: api_resource=%s", __FUNCTION__, api_resource);
if (query != NULL && strlen(query)) if (query != NULL && strlen(query))
if (str2cvec(query, '&', '=', &qvec) < 0) if (str2cvec(query, '&', '=', &qvec) < 0)
goto done; goto done;
if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */ if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */
goto done; goto done;
/* data */ /* indata */
if ((cb = readdata(req)) == NULL) if ((cb = restconf_get_indata(req)) == NULL)
goto done; goto done;
data = cbuf_get(cb); indata = cbuf_get(cb);
clicon_debug(1, "%s DATA=%s", __FUNCTION__, data); clicon_debug(1, "%s DATA=%s", __FUNCTION__, indata);
if (str2cvec(data, '&', '=', &dvec) < 0)
goto done;
/* If present, check credentials. See "plugin_credentials" in plugin /* If present, check credentials. See "plugin_credentials" in plugin
* See RFC 8040 section 2.5 * See RFC 8040 section 2.5
*/ */
@ -429,7 +467,7 @@ api_restconf(clicon_handle h,
goto done; goto done;
clicon_debug(1, "%s auth:%d %s", __FUNCTION__, authenticated, clicon_username_get(h)); clicon_debug(1, "%s auth:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
/* If set but no user, we set a dummy user */ /* If set but no user, set a dummy user */
if (authenticated){ if (authenticated){
if (clicon_username_get(h) == NULL) if (clicon_username_get(h) == NULL)
clicon_username_set(h, "none"); clicon_username_set(h, "none");
@ -445,22 +483,20 @@ api_restconf(clicon_handle h,
goto ok; goto ok;
} }
clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h)); clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
if (strcmp(method, "yang-library-version")==0){ if (strcmp(api_resource, "yang-library-version")==0){
if (api_yang_library_version(h, req, pretty, media_out) < 0) if (api_yang_library_version(h, req, pretty, media_out) < 0)
goto done; goto done;
} }
else if (strcmp(method, "data") == 0){ /* restconf, skip /api/data */ else if (strcmp(api_resource, "data") == 0){ /* restconf, skip /api/data */
if (api_data(h, req, path, pcvec, 2, qvec, data, if (api_data(h, req, path, pcvec, 2, qvec, indata,
pretty, media_out) < 0) pretty, media_out) < 0)
goto done; goto done;
} }
else if (strcmp(method, "operations") == 0){ /* rpc */ else if (strcmp(api_resource, "operations") == 0){ /* rpc */
if (api_operations(h, req, path, pcvec, 2, qvec, data, if (api_operations(h, req, path, pcvec, 2, qvec, indata,
pretty, media_out) < 0) pretty, media_out) < 0)
goto done; goto done;
} }
else if (strcmp(method, "test") == 0)
restconf_test(req, 0);
else else
restconf_notfound(h, req); restconf_notfound(h, req);
ok: ok:
@ -469,16 +505,12 @@ api_restconf(clicon_handle h,
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (pvec) if (pvec)
free(pvec); free(pvec);
if (dvec)
cvec_free(dvec);
if (qvec) if (qvec)
cvec_free(qvec); cvec_free(qvec);
if (pcvec) if (pcvec)
cvec_free(pcvec); cvec_free(pcvec);
if (cb) if (cb)
cbuf_free(cb); cbuf_free(cb);
if (cbret)
cbuf_free(cbret);
if (xret) if (xret)
xml_free(xret); xml_free(xret);
return retval; return retval;
@ -834,12 +866,16 @@ main(int argc,
/* Translate from FCGI parameter form to Clixon runtime data /* Translate from FCGI parameter form to Clixon runtime data
* XXX: potential name collision? * XXX: potential name collision?
*/ */
if (clixon_restconf_params_set(h, req->envp) < 0) if (fcgi_params_set(h, req->envp) < 0)
goto done; goto done;
/* Debug_ print headers */
if (clicon_debug_get())
fcgi_print_headers(req);
if ((path = clixon_restconf_param_get(h, "REQUEST_URI")) != NULL){ if ((path = clixon_restconf_param_get(h, "REQUEST_URI")) != NULL){
clicon_debug(1, "path: %s", path); clicon_debug(1, "path: %s", path);
if (strncmp(path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0) if (strncmp(path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0)
api_restconf(h, req); /* This is the function */ api_fcgi_restconf(h, req); /* This is the function */
else if (strncmp(path+1, stream_path, strlen(stream_path)) == 0) { else if (strncmp(path+1, stream_path, strlen(stream_path)) == 0) {
api_stream(h, req, stream_path, &finish); api_stream(h, req, stream_path, &finish);
} }
@ -853,7 +889,7 @@ main(int argc,
} }
else else
clicon_debug(1, "NULL URI"); clicon_debug(1, "NULL URI");
if (clixon_restconf_params_clear(h, req->envp) < 0) if (fcgi_params_clear(h, req->envp) < 0)
goto done; goto done;
if (finish) if (finish)
FCGX_Finish_r(req); FCGX_Finish_r(req);

View file

@ -478,7 +478,6 @@ restconf_uripath(clicon_handle h)
return path; return path;
} }
/*! Drop privileges from root to user (or already at user) /*! Drop privileges from root to user (or already at user)
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] user Drop to this level * @param[in] user Drop to this level
@ -540,15 +539,3 @@ restconf_drop_privileges(clicon_handle h,
return retval; return retval;
} }
/*! HTTP error 405
* @param[in] req Generic Www handle
* @param[in] allow Which methods are allowed
*/
int
restconf_method_notallowed(void *req,
char *allow)
{
restconf_reply_status_code(req, 405);
restconf_reply_header_add(req, "Allow", "%s", allow);
return 0;
}

View file

@ -69,6 +69,5 @@ int clixon_restconf_param_set(clicon_handle h, char *param, char *val);
int clixon_restconf_param_del(clicon_handle h, char *param); int clixon_restconf_param_del(clicon_handle h, char *param);
char *restconf_uripath(clicon_handle h); char *restconf_uripath(clicon_handle h);
int restconf_drop_privileges(clicon_handle h, char *user); int restconf_drop_privileges(clicon_handle h, char *user);
int restconf_method_notallowed(void *req, char *allow);
#endif /* _RESTCONF_LIB_H_ */ #endif /* _RESTCONF_LIB_H_ */

View file

@ -119,16 +119,16 @@ Mapping netconf error-tag -> status code
/* clicon */ /* clicon */
#include <clixon/clixon.h> #include <clixon/clixon.h>
#include <fcgiapp.h> /* Need to be after clixon_xml-h due to attribute format */
#include "restconf_lib.h" #include "restconf_lib.h"
#include "restconf_fcgi_lib.h" #include "restconf_api.h"
#include "restconf_err.h"
#include "restconf_methods.h" #include "restconf_methods.h"
/*! REST OPTIONS method /*! REST OPTIONS method
* According to restconf * According to restconf
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] r Fastcgi request handle * @param[in] req Generic Www handle
*
* @code * @code
* curl -G http://localhost/restconf/data/interfaces/interface=eth0 * curl -G http://localhost/restconf/data/interfaces/interface=eth0
* @endcode * @endcode
@ -139,14 +139,20 @@ Mapping netconf error-tag -> status code
*/ */
int int
api_data_options(clicon_handle h, api_data_options(clicon_handle h,
FCGX_Request *r) void *req)
{ {
int retval = -1;
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
FCGX_SetExitStatus(200, r->out); /* OK */ if (restconf_reply_header(req, "Allow", "OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE") < 0)
FCGX_FPrintF(r->out, "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE\r\n"); goto done;
FCGX_FPrintF(r->out, "Accept-Patch: application/yang-data+xml,application/yang-data+json\r\n"); if (restconf_reply_header(req, "Accept-Patch", "application/yang-data+xml,application/yang-data+json") < 0)
FCGX_FPrintF(r->out, "\r\n"); goto done;
return 0; if (restconf_reply_send(req, 200, NULL) < 0)
goto done;
retval = 0;
done:
return retval;
} }
/*! Check matching keys /*! Check matching keys
@ -226,7 +232,7 @@ match_list_keys(yang_stmt *y,
*/ */
static int static int
api_data_write(clicon_handle h, api_data_write(clicon_handle h,
FCGX_Request *r, void *req,
char *api_path0, char *api_path0,
cvec *pcvec, cvec *pcvec,
int pi, int pi,
@ -285,7 +291,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -300,7 +306,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
@ -311,7 +317,7 @@ api_data_write(clicon_handle h,
#endif #endif
if (xml_child_nr(xret) == 0){ /* Object does not exist */ if (xml_child_nr(xret) == 0){ /* Object does not exist */
if (plain_patch){ /* If the target resource instance does not exist, the server MUST NOT create it. */ if (plain_patch){ /* If the target resource instance does not exist, the server MUST NOT create it. */
restconf_badrequest(h, r); restconf_badrequest(h, req);
goto ok; goto ok;
} }
else else
@ -340,7 +346,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -357,7 +363,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -400,7 +406,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -409,7 +415,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -422,7 +428,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -431,13 +437,13 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
break; break;
default: default:
restconf_unsupported_media(r); restconf_unsupported_media(req);
goto ok; goto ok;
break; break;
} /* switch media_in */ } /* switch media_in */
@ -452,7 +458,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -471,7 +477,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -520,7 +526,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -545,7 +551,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -569,7 +575,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -610,7 +616,7 @@ api_data_write(clicon_handle h,
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0)
goto done; goto done;
if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){ if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -631,7 +637,7 @@ api_data_write(clicon_handle h,
/* log errors from discard, but ignore */ /* log errors from discard, but ignore */
if ((xpath_first(xretdis, NULL, "//rpc-error")) != NULL) if ((xpath_first(xretdis, NULL, "//rpc-error")) != NULL)
clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__);
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -659,14 +665,13 @@ api_data_write(clicon_handle h,
} }
/* Check if it was created, or if we tried again and replaced it */ /* Check if it was created, or if we tried again and replaced it */
if (op == OP_CREATE){ if (op == OP_CREATE){
FCGX_SetExitStatus(201, r->out); /* Created */ if (restconf_reply_send(req, 201, NULL) < 0)
FCGX_FPrintF(r->out, "Status: 201 Created\r\n"); goto done;
} }
else{ else{
FCGX_SetExitStatus(204, r->out); /* Replaced */ if (restconf_reply_send(req, 204, NULL) < 0)
FCGX_FPrintF(r->out, "Status: 204 No Content\r\n"); goto done;
} }
FCGX_FPrintF(r->out, "\r\n");
ok: ok:
retval = 0; retval = 0;
done: done:
@ -693,8 +698,8 @@ api_data_write(clicon_handle h,
} /* api_data_write */ } /* api_data_write */
/*! Generic REST PUT method /*! Generic REST PUT method
* @param[in] h CLIXON handle * @param[in] h Clixon handle
* @param[in] r Fastcgi request handle * @param[in] req Generic Www handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec * @param[in] pi Offset, where to start pcvec
@ -729,7 +734,7 @@ api_data_write(clicon_handle h,
*/ */
int int
api_data_put(clicon_handle h, api_data_put(clicon_handle h,
FCGX_Request *r, void *req,
char *api_path0, char *api_path0,
cvec *pcvec, cvec *pcvec,
int pi, int pi,
@ -741,13 +746,13 @@ api_data_put(clicon_handle h,
restconf_media media_in; restconf_media media_in;
media_in = restconf_content_type(h); media_in = restconf_content_type(h);
return api_data_write(h, r, api_path0, pcvec, pi, qvec, data, pretty, return api_data_write(h, req, api_path0, pcvec, pi, qvec, data, pretty,
media_in, media_out, 0); media_in, media_out, 0);
} }
/*! Generic REST PATCH method for plain patch /*! Generic REST PATCH method for plain patch
* @param[in] h CLIXON handle * @param[in] h Clixon handle
* @param[in] r Fastcgi request handle * @param[in] req Generic Www handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec * @param[in] pi Offset, where to start pcvec
@ -764,7 +769,7 @@ api_data_put(clicon_handle h,
*/ */
int int
api_data_patch(clicon_handle h, api_data_patch(clicon_handle h,
FCGX_Request *r, void *req,
char *api_path0, char *api_path0,
cvec *pcvec, cvec *pcvec,
int pi, int pi,
@ -780,23 +785,23 @@ api_data_patch(clicon_handle h,
switch (media_in){ switch (media_in){
case YANG_DATA_XML: case YANG_DATA_XML:
case YANG_DATA_JSON: /* plain patch */ case YANG_DATA_JSON: /* plain patch */
ret = api_data_write(h, r, api_path0, pcvec, pi, qvec, data, pretty, ret = api_data_write(h, req, api_path0, pcvec, pi, qvec, data, pretty,
media_in, media_out, 1); media_in, media_out, 1);
break; break;
case YANG_PATCH_XML: case YANG_PATCH_XML:
case YANG_PATCH_JSON: /* RFC 8072 patch */ case YANG_PATCH_JSON: /* RFC 8072 patch */
ret = restconf_notimplemented(r); ret = restconf_notimplemented(req);
break; break;
default: default:
ret = restconf_unsupported_media(r); ret = restconf_unsupported_media(req);
break; break;
} }
return ret; return ret;
} }
/*! Generic REST DELETE method translated to edit-config /*! Generic REST DELETE method translated to edit-config
* @param[in] h CLIXON handle * @param[in] h Clixon handle
* @param[in] r Fastcgi request handle * @param[in] req Generic Www handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pi Offset, where path starts * @param[in] pi Offset, where path starts
* @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] pretty Set to 1 for pretty-printed xml/json output
@ -808,7 +813,7 @@ api_data_patch(clicon_handle h,
*/ */
int int
api_data_delete(clicon_handle h, api_data_delete(clicon_handle h,
FCGX_Request *r, void *req,
char *api_path, char *api_path,
int pi, int pi,
int pretty, int pretty,
@ -850,7 +855,7 @@ api_data_delete(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -879,7 +884,7 @@ api_data_delete(clicon_handle h,
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0)
goto done; goto done;
if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){ if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -901,7 +906,7 @@ api_data_delete(clicon_handle h,
/* log errors from discard, but ignore */ /* log errors from discard, but ignore */
if ((xpath_first(xretdis, NULL, "//rpc-error")) != NULL) if ((xpath_first(xretdis, NULL, "//rpc-error")) != NULL)
clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__);
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -927,10 +932,8 @@ api_data_delete(clicon_handle h,
clicon_log(LOG_WARNING, "%s: copy-config running->startup failed", __FUNCTION__); clicon_log(LOG_WARNING, "%s: copy-config running->startup failed", __FUNCTION__);
} }
} }
FCGX_SetExitStatus(204, r->out); if (restconf_reply_send(req, 204, NULL) < 0)
FCGX_FPrintF(r->out, "Status: 204 No Content\r\n"); goto done;
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
ok: ok:
retval = 0; retval = 0;
done: done:

View file

@ -3,6 +3,7 @@
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON. This file is part of CLIXON.
@ -41,18 +42,18 @@
/* /*
* Prototypes * Prototypes
*/ */
int api_data_options(clicon_handle h, FCGX_Request *r); int api_data_options(clicon_handle h, void *req);
int api_data_put(clicon_handle h, FCGX_Request *r, char *api_path, int api_data_put(clicon_handle h, void *req, char *api_path,
cvec *pcvec, int pi, cvec *pcvec, int pi,
cvec *qvec, char *data, cvec *qvec, char *data,
int pretty, restconf_media media_out); int pretty, restconf_media media_out);
int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path, int api_data_patch(clicon_handle h, void *req, char *api_path,
cvec *pcvec, int pi, cvec *pcvec, int pi,
cvec *qvec, char *data, int pretty, cvec *qvec, char *data, int pretty,
restconf_media media_out); restconf_media media_out);
int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi, int api_data_delete(clicon_handle h, void *req, char *api_path, int pi,
int pretty, restconf_media media_out); int pretty, restconf_media media_out);
#endif /* _RESTCONF_METHODS_H_ */ #endif /* _RESTCONF_METHODS_H_ */

View file

@ -57,16 +57,15 @@
/* clicon */ /* clicon */
#include <clixon/clixon.h> #include <clixon/clixon.h>
#include <fcgiapp.h> /* Need to be after clixon_xml-h due to attribute format */
#include "restconf_lib.h" #include "restconf_lib.h"
#include "restconf_fcgi_lib.h" #include "restconf_api.h"
#include "restconf_err.h"
#include "restconf_methods_get.h" #include "restconf_methods_get.h"
/*! Generic GET (both HEAD and GET) /*! Generic GET (both HEAD and GET)
* According to restconf * According to restconf
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] r Fastcgi request handle * @param[in] req Generic Www handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts * @param[in] pi Offset, where path starts
@ -93,7 +92,7 @@
*/ */
static int static int
api_data_get2(clicon_handle h, api_data_get2(clicon_handle h,
FCGX_Request *r, void *req,
char *api_path, char *api_path,
cvec *pcvec, /* XXX remove? */ cvec *pcvec, /* XXX remove? */
int pi, int pi,
@ -148,7 +147,7 @@ api_data_get2(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -165,7 +164,7 @@ api_data_get2(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -187,7 +186,7 @@ api_data_get2(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -213,7 +212,7 @@ api_data_get2(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -226,7 +225,7 @@ api_data_get2(clicon_handle h,
#endif #endif
/* Check if error return */ /* Check if error return */
if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){ if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -234,9 +233,13 @@ api_data_get2(clicon_handle h,
if ((cbx = cbuf_new()) == NULL) if ((cbx = cbuf_new()) == NULL)
goto done; goto done;
if (head){ if (head){
FCGX_SetExitStatus(200, r->out); /* OK */ /* Same headers as the GET, but no body */
FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out)); if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0)
FCGX_FPrintF(r->out, "\r\n"); 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; goto ok;
} }
if (xpath==NULL || strcmp(xpath,"/")==0){ /* Special case: data root */ if (xpath==NULL || strcmp(xpath,"/")==0){ /* Special case: data root */
@ -261,7 +264,7 @@ api_data_get2(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -275,7 +278,7 @@ api_data_get2(clicon_handle h,
goto done; goto done;
/* override invalid-value default 400 with 404 */ /* override invalid-value default 400 with 404 */
if ((xe = xpath_first(xerr, NULL, "rpc-error")) != NULL){ if ((xe = xpath_first(xerr, NULL, "rpc-error")) != NULL){
if (api_return_err(h, r, xe, pretty, media_out, 404) < 0) if (api_return_err(h, req, xe, pretty, media_out, 404) < 0)
goto done; goto done;
} }
goto ok; goto ok;
@ -309,12 +312,12 @@ api_data_get2(clicon_handle h,
} }
} }
clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx)); clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx));
FCGX_SetExitStatus(200, r->out); /* OK */ if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0)
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n"); goto done;
FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out)); if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0)
FCGX_FPrintF(r->out, "\r\n"); goto done;
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); if (restconf_reply_send(req, 200, cbx) < 0)
FCGX_FPrintF(r->out, "\r\n\r\n"); goto done;
ok: ok:
retval = 0; retval = 0;
done: done:
@ -338,7 +341,7 @@ api_data_get2(clicon_handle h,
/*! REST HEAD method /*! REST HEAD method
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] r Fastcgi request handle * @param[in] req Generic Www handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts * @param[in] pi Offset, where path starts
@ -353,7 +356,7 @@ api_data_get2(clicon_handle h,
*/ */
int int
api_data_head(clicon_handle h, api_data_head(clicon_handle h,
FCGX_Request *r, void *req,
char *api_path, char *api_path,
cvec *pcvec, cvec *pcvec,
int pi, int pi,
@ -361,13 +364,13 @@ api_data_head(clicon_handle h,
int pretty, int pretty,
restconf_media media_out) restconf_media media_out)
{ {
return api_data_get2(h, r, api_path, pcvec, pi, qvec, pretty, media_out, 1); return api_data_get2(h, req, api_path, pcvec, pi, qvec, pretty, media_out, 1);
} }
/*! REST GET method /*! REST GET method
* According to restconf * According to restconf
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] r Fastcgi request handle * @param[in] req Generic Www handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts * @param[in] pi Offset, where path starts
@ -392,7 +395,7 @@ api_data_head(clicon_handle h,
*/ */
int int
api_data_get(clicon_handle h, api_data_get(clicon_handle h,
FCGX_Request *r, void *req,
char *api_path, char *api_path,
cvec *pcvec, cvec *pcvec,
int pi, int pi,
@ -400,12 +403,12 @@ api_data_get(clicon_handle h,
int pretty, int pretty,
restconf_media media_out) restconf_media media_out)
{ {
return api_data_get2(h, r, api_path, pcvec, pi, qvec, pretty, media_out, 0); return api_data_get2(h, req, api_path, pcvec, pi, qvec, pretty, media_out, 0);
} }
/*! GET restconf/operations resource /*! GET restconf/operations resource
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] r Fastcgi request handle * @param[in] req Generic Www handle
* @param[in] path According to restconf (Sec 3.5.1.1 in [draft]) * @param[in] path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts * @param[in] pi Offset, where path starts
@ -430,7 +433,7 @@ api_data_get(clicon_handle h,
*/ */
int int
api_operations_get(clicon_handle h, api_operations_get(clicon_handle h,
FCGX_Request *r, void *req,
char *path, char *path,
int pi, int pi,
cvec *qvec, cvec *qvec,
@ -505,11 +508,13 @@ api_operations_get(clicon_handle h,
default: default:
break; break;
} }
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out)); if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0)
FCGX_FPrintF(r->out, "\r\n"); goto done;
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0)
FCGX_FPrintF(r->out, "\r\n\r\n"); goto done;
if (restconf_reply_send(req, 200, cbx) < 0)
goto done;
// ok: // ok:
retval = 0; retval = 0;
done: done:

View file

@ -3,6 +3,7 @@
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON. This file is part of CLIXON.
@ -40,11 +41,11 @@
/* /*
* Prototypes * Prototypes
*/ */
int api_data_head(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi, int api_data_head(clicon_handle h, void *req, char *api_path, cvec *pcvec, int pi,
cvec *qvec, int pretty, restconf_media media_out); cvec *qvec, int pretty, restconf_media media_out);
int api_data_get(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi, int api_data_get(clicon_handle h, void *req, char *api_path, cvec *pcvec, int pi,
cvec *qvec, int pretty, restconf_media media_out); cvec *qvec, int pretty, restconf_media media_out);
int api_operations_get(clicon_handle h, FCGX_Request *r, int api_operations_get(clicon_handle h, void *req,
char *api_path, int pi, cvec *qvec, char *data, char *api_path, int pi, cvec *qvec, char *data,
int pretty, restconf_media media_out); int pretty, restconf_media media_out);

View file

@ -59,15 +59,61 @@
/* clicon */ /* clicon */
#include <clixon/clixon.h> #include <clixon/clixon.h>
#include <fcgiapp.h> /* Need to be after clixon_xml.h due to attribute format */
#include "restconf_lib.h" #include "restconf_lib.h"
#include "restconf_fcgi_lib.h" #include "restconf_api.h"
#include "restconf_err.h"
#include "restconf_methods_post.h" #include "restconf_methods_post.h"
/*! Print location header from
* @param[in] req Generic Www handle
* @param[in] xobj If set (eg POST) add to api-path
* $https on if connection operates in SSL mode, or an empty string otherwise
* @note ports are ignored
*/
static int
http_location_header(clicon_handle h,
void *req,
cxobj *xobj)
{
int retval = -1;
char *https;
char *host;
char *request_uri;
cbuf *cb = NULL;
https = clixon_restconf_param_get(h, "HTTPS");
host = clixon_restconf_param_get(h, "HTTP_HOST");
request_uri = clixon_restconf_param_get(h, "REQUEST_URI");
if (xobj != NULL){
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, 0, "cbuf_new");
goto done;
}
if (xml2api_path_1(xobj, cb) < 0)
goto done;
if (restconf_reply_header(req, "Location", "http%s://%s%s%s",
https?"s":"",
host,
request_uri,
cbuf_get(cb)) < 0)
goto done;
}
else
if (restconf_reply_header(req, "Location", "http%s://%s%s",
https?"s":"",
host,
request_uri) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Generic REST POST method /*! Generic REST POST method
* @param[in] h CLIXON handle * @param[in] h Clixon handle
* @param[in] r Fastcgi request handle * @param[in] req Generic Www handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec * @param[in] pi Offset, where to start pcvec
@ -99,7 +145,7 @@
*/ */
int int
api_data_post(clicon_handle h, api_data_post(clicon_handle h,
FCGX_Request *r, void *req,
char *api_path, char *api_path,
int pi, int pi,
cvec *qvec, cvec *qvec,
@ -153,7 +199,7 @@ api_data_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -168,7 +214,7 @@ api_data_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -198,7 +244,7 @@ api_data_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -207,7 +253,7 @@ api_data_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -220,7 +266,7 @@ api_data_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -229,13 +275,13 @@ api_data_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
break; break;
default: default:
restconf_unsupported_media(r); restconf_unsupported_media(req);
goto ok; goto ok;
break; break;
} /* switch media_in */ } /* switch media_in */
@ -251,7 +297,7 @@ api_data_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -284,7 +330,7 @@ api_data_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
@ -298,7 +344,7 @@ api_data_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -332,7 +378,7 @@ api_data_post(clicon_handle h,
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0)
goto done; goto done;
if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){ if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -354,7 +400,7 @@ api_data_post(clicon_handle h,
/* log errors from discard, but ignore */ /* log errors from discard, but ignore */
if ((xpath_first(xretdis, NULL, "//rpc-error")) != NULL) if ((xpath_first(xretdis, NULL, "//rpc-error")) != NULL)
clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__);
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) /* Use original xe */ if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) /* Use original xe */
goto done; goto done;
goto ok; goto ok;
} }
@ -376,14 +422,13 @@ api_data_post(clicon_handle h,
goto done; goto done;
/* If copy-config failed, log and ignore (already committed) */ /* If copy-config failed, log and ignore (already committed) */
if ((xe = xpath_first(xretcom, NULL, "//rpc-error")) != NULL){ if ((xe = xpath_first(xretcom, NULL, "//rpc-error")) != NULL){
clicon_log(LOG_WARNING, "%s: copy-config running->startup failed", __FUNCTION__); clicon_log(LOG_WARNING, "%s: copy-config running->startup failed", __FUNCTION__);
} }
} }
FCGX_SetExitStatus(201, r->out); if (http_location_header(h, req, xdata) < 0)
FCGX_FPrintF(r->out, "Status: 201 Created\r\n"); goto done;
http_location(h, r, xdata); if (restconf_reply_send(req, 201, NULL) < 0)
FCGX_FPrintF(r->out, "\r\n"); goto done;
ok: ok:
retval = 0; retval = 0;
done: done:
@ -404,8 +449,8 @@ api_data_post(clicon_handle h,
} /* api_data_post */ } /* api_data_post */
/*! Handle input data to api_operations_post /*! Handle input data to api_operations_post
* @param[in] h CLIXON handle * @param[in] h Clixon handle
* @param[in] r Fastcgi request handle * @param[in] req Generic Www handle
* @param[in] data Stream input data * @param[in] data Stream input data
* @param[in] yspec Yang top-level specification * @param[in] yspec Yang top-level specification
* @param[in] yrpc Yang rpc spec * @param[in] yrpc Yang rpc spec
@ -426,7 +471,7 @@ api_data_post(clicon_handle h,
*/ */
static int static int
api_operations_post_input(clicon_handle h, api_operations_post_input(clicon_handle h,
FCGX_Request *r, void *req,
char *data, char *data,
yang_stmt *yspec, yang_stmt *yspec,
yang_stmt *yrpc, yang_stmt *yrpc,
@ -462,17 +507,16 @@ api_operations_post_input(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto fail; goto fail;
} }
if (ret == 0){ if (ret == 0){
if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){
clicon_debug(1, "%s F", __FUNCTION__);
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto fail; goto fail;
} }
@ -487,7 +531,7 @@ api_operations_post_input(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto fail; goto fail;
} }
@ -496,17 +540,16 @@ api_operations_post_input(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto fail; goto fail;
} }
break; break;
default: default:
restconf_unsupported_media(r); restconf_unsupported_media(req);
goto fail; goto fail;
break; break;
} /* switch media_in */ } /* switch media_in */
clicon_debug(1, "%s F", __FUNCTION__);
xml_name_set(xdata, "data"); xml_name_set(xdata, "data");
/* Here xdata is: /* Here xdata is:
* <data><input xmlns="urn:example:clixon">...</input></data> * <data><input xmlns="urn:example:clixon">...</input></data>
@ -531,7 +574,7 @@ api_operations_post_input(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto fail; goto fail;
} }
@ -560,8 +603,8 @@ api_operations_post_input(clicon_handle h,
} }
/*! Handle output data to api_operations_post /*! Handle output data to api_operations_post
* @param[in] h CLIXON handle * @param[in] h Clixon handle
* @param[in] r Fastcgi request handle * @param[in] req Generic Www handle
* @param[in] xret XML reply messages from backend/handler * @param[in] xret XML reply messages from backend/handler
* @param[in] yspec Yang top-level specification * @param[in] yspec Yang top-level specification
* @param[in] youtput Yang rpc output specification * @param[in] youtput Yang rpc output specification
@ -575,7 +618,7 @@ api_operations_post_input(clicon_handle h,
*/ */
static int static int
api_operations_post_output(clicon_handle h, api_operations_post_output(clicon_handle h,
FCGX_Request *r, void *req,
cxobj *xret, cxobj *xret,
yang_stmt *yspec, yang_stmt *yspec,
yang_stmt *youtput, yang_stmt *youtput,
@ -605,7 +648,7 @@ api_operations_post_output(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto fail; goto fail;
} }
@ -638,7 +681,7 @@ api_operations_post_output(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto fail; goto fail;
} }
@ -659,9 +702,8 @@ api_operations_post_output(clicon_handle h,
strcmp(xml_name(xok),"ok")==0); strcmp(xml_name(xok),"ok")==0);
if (isempty) { if (isempty) {
/* Internal error - invalid output from rpc handler */ /* Internal error - invalid output from rpc handler */
FCGX_SetExitStatus(204, r->out); /* OK */ if (restconf_reply_send(req, 204, NULL) < 0)
FCGX_FPrintF(r->out, "Status: 204 No Content\r\n"); goto done;
FCGX_FPrintF(r->out, "\r\n");
goto fail; goto fail;
} }
/* Clear namespace of parameters */ /* Clear namespace of parameters */
@ -687,8 +729,8 @@ api_operations_post_output(clicon_handle h,
} }
/*! REST operation POST method /*! REST operation POST method
* @param[in] h CLIXON handle * @param[in] h Clixon handle
* @param[in] r Fastcgi request handle * @param[in] req Generic Www handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data * @param[in] data Stream input data
@ -717,7 +759,7 @@ api_operations_post_output(clicon_handle h,
*/ */
int int
api_operations_post(clicon_handle h, api_operations_post(clicon_handle h,
FCGX_Request *r, void *req,
char *api_path, char *api_path,
int pi, int pi,
cvec *qvec, cvec *qvec,
@ -766,7 +808,7 @@ api_operations_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -785,7 +827,7 @@ api_operations_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -796,7 +838,7 @@ api_operations_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -821,7 +863,7 @@ api_operations_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -834,7 +876,7 @@ api_operations_post(clicon_handle h,
namespace = xml_find_type_value(xbot, NULL, "xmlns", CX_ATTR); namespace = xml_find_type_value(xbot, NULL, "xmlns", CX_ATTR);
clicon_debug(1, "%s : 4. Parse input data: %s", __FUNCTION__, data); clicon_debug(1, "%s : 4. Parse input data: %s", __FUNCTION__, data);
if (data && strlen(data)){ if (data && strlen(data)){
if ((ret = api_operations_post_input(h, r, data, yspec, yrpc, xbot, if ((ret = api_operations_post_input(h, req, data, yspec, yrpc, xbot,
pretty, media_out)) < 0) pretty, media_out)) < 0)
goto done; goto done;
if (ret == 0) if (ret == 0)
@ -854,7 +896,7 @@ api_operations_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto ok; goto ok;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -866,7 +908,7 @@ api_operations_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto ok; goto ok;
} }
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -884,14 +926,14 @@ api_operations_post(clicon_handle h,
/* Look for local (client-side) restconf plugins. /* Look for local (client-side) restconf plugins.
* -1:Error, 0:OK local, 1:OK backend * -1:Error, 0:OK local, 1:OK backend
*/ */
if ((ret = rpc_callback_call(h, xbot, cbret, r)) < 0) if ((ret = rpc_callback_call(h, xbot, cbret, req)) < 0)
goto done; goto done;
if (ret > 0){ /* Handled locally */ if (ret > 0){ /* Handled locally */
if (clixon_xml_parse_string(cbuf_get(cbret), YB_NONE, NULL, &xret, NULL) < 0) if (clixon_xml_parse_string(cbuf_get(cbret), YB_NONE, NULL, &xret, NULL) < 0)
goto done; goto done;
/* Local error: return it and quit */ /* Local error: return it and quit */
if ((xe = xpath_first(xret, NULL, "rpc-reply/rpc-error")) != NULL){ if ((xe = xpath_first(xret, NULL, "rpc-reply/rpc-error")) != NULL){
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -900,7 +942,7 @@ api_operations_post(clicon_handle h,
if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0)
goto done; goto done;
if ((xe = xpath_first(xret, NULL, "rpc-reply/rpc-error")) != NULL){ if ((xe = xpath_first(xret, NULL, "rpc-reply/rpc-error")) != NULL){
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -913,16 +955,14 @@ api_operations_post(clicon_handle h,
clicon_log_xml(LOG_DEBUG, xret, "%s Receive reply:", __FUNCTION__); clicon_log_xml(LOG_DEBUG, xret, "%s Receive reply:", __FUNCTION__);
#endif #endif
youtput = yang_find(yrpc, Y_OUTPUT, NULL); youtput = yang_find(yrpc, Y_OUTPUT, NULL);
if ((ret = api_operations_post_output(h, r, xret, yspec, youtput, namespace, if ((ret = api_operations_post_output(h, req, xret, yspec, youtput, namespace,
pretty, media_out, &xoutput)) < 0) pretty, media_out, &xoutput)) < 0)
goto done; goto done;
if (ret == 0) if (ret == 0)
goto ok; goto ok;
/* xoutput should now look: <output xmlns="uri"><x>0</x></output> */ /* xoutput should now look: <output xmlns="uri"><x>0</x></output> */
FCGX_SetExitStatus(200, r->out); /* OK */ if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0)
goto done;
FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out));
FCGX_FPrintF(r->out, "\r\n");
cbuf_reset(cbret); cbuf_reset(cbret);
switch (media_out){ switch (media_out){
case YANG_DATA_XML: case YANG_DATA_XML:
@ -938,8 +978,8 @@ api_operations_post(clicon_handle h,
default: default:
break; break;
} }
FCGX_FPrintF(r->out, "%s", cbuf_get(cbret)); if (restconf_reply_send(req, 200, cbret) < 0)
FCGX_FPrintF(r->out, "\r\n\r\n"); goto done;
ok: ok:
retval = 0; retval = 0;
done: done:

View file

@ -3,6 +3,7 @@
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON. This file is part of CLIXON.
@ -34,22 +35,19 @@
* Restconf method implementation for post: operation(rpc) and data * Restconf method implementation for post: operation(rpc) and data
*/ */
#ifndef _RESTCONF_METHODS_POST_H_ #ifndef _RESTCONF_METHODS_POST_H_
#define _RESTCONF_METHODS_POST_H_ #define _RESTCONF_METHODS_POST_H_
/* /*
* Prototypes * Prototypes
*/ */
int api_data_post(clicon_handle h, FCGX_Request *r, char *api_path, int api_data_post(clicon_handle h, void *req, char *api_path,
int pi, int pi, cvec *qvec, char *data,
cvec *qvec, char *data,
int pretty, int pretty,
restconf_media media_out); restconf_media media_out);
int api_operations_post(clicon_handle h, FCGX_Request *r, char *api_path, int api_operations_post(clicon_handle h, void *req, char *api_path,
int pi, cvec *qvec, char *data, int pi, cvec *qvec, char *data,
int pretty, restconf_media media_out); int pretty, restconf_media media_out);
#endif /* _RESTCONF_METHODS_POST_H_ */ #endif /* _RESTCONF_METHODS_POST_H_ */

View file

@ -63,13 +63,13 @@
/* restconf */ /* restconf */
#include "restconf_lib.h" #include "restconf_lib.h"
#include "restconf_api.h" #include "restconf_api.h"
#include "restconf_err.h"
#include "restconf_root.h" #include "restconf_root.h"
/*! Determine the root of the RESTCONF API /*! Determine the root of the RESTCONF API
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] req Generic Www handle (can be part of clixon handle) * @param[in] req Generic Www handle (can be part of clixon handle)
* @param[in] cb Body buffer
* @see RFC8040 3.1 and RFC7320 * @see RFC8040 3.1 and RFC7320
* In line with the best practices defined by [RFC7320], RESTCONF * In line with the best practices defined by [RFC7320], RESTCONF
* enables deployments to specify where the RESTCONF API is located. * enables deployments to specify where the RESTCONF API is located.
@ -92,9 +92,10 @@ api_well_known(clicon_handle h,
restconf_method_notallowed(req, "GET"); restconf_method_notallowed(req, "GET");
goto ok; goto ok;
} }
restconf_reply_status_code(req, 200); /* OK */ if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0)
restconf_reply_header_add(req, "Cache-Control", "no-cache"); goto done;
restconf_reply_header_add(req, "Content-Type", "application/xrd+xml"); if (restconf_reply_header(req, "Content-Type", "application/xrd+xml") < 0)
goto done;
/* Create body */ /* Create body */
if ((cb = cbuf_new()) == NULL){ if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new"); clicon_err(OE_UNIX, errno, "cbuf_new");
@ -105,8 +106,9 @@ api_well_known(clicon_handle h,
cprintf(cb, "</XRD>\r\n"); cprintf(cb, "</XRD>\r\n");
/* Must be after body */ /* Must be after body */
restconf_reply_header_add(req, "Content-Length", "%d", cbuf_len(cb)); if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)
if (restconf_reply_send(req, cb) < 0) goto done;
if (restconf_reply_send(req, 200, cb) < 0)
goto done; goto done;
ok: ok:
retval = 0; retval = 0;
@ -116,3 +118,461 @@ api_well_known(clicon_handle h,
return retval; return retval;
} }
/*! Retrieve the Top-Level API Resource
* @param[in] h Clicon handle
* @param[in] r Fastcgi request handle
* @note Only returns null for operations and data,...
* See RFC8040 3.3
* XXX doesnt check method
*/
static int
api_root(clicon_handle h,
void *req,
char *request_method,
int pretty,
restconf_media media_out)
{
int retval = -1;
yang_stmt *yspec;
cxobj *xt = NULL;
cbuf *cb = NULL;
clicon_debug(1, "%s", __FUNCTION__);
if (strcmp(request_method, "GET") != 0){
restconf_method_notallowed(req, "GET");
goto ok;
}
if ((yspec = clicon_dbspec_yang(h)) == NULL){
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 (clixon_xml_parse_string("<restconf xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><data/>"
"<operations/><yang-library-version>2016-06-21</yang-library-version></restconf>",
YB_MODULE, yspec, &xt, NULL) < 0)
goto done;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
switch (media_out){
case YANG_DATA_XML:
if (clicon_xml2cbuf(cb, xt, 0, pretty, -1) < 0)
goto done;
break;
case YANG_DATA_JSON:
if (xml2json_cbuf(cb, xt, pretty) < 0)
goto done;
break;
default:
break;
}
if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)
goto done;
if (restconf_reply_send(req, 200, cb) < 0)
goto done;
ok:
retval = 0;
done:
if (cb)
cbuf_free(cb);
if (xt)
xml_free(xt);
return retval;
}
/*!
* See https://tools.ietf.org/html/rfc7895
*/
static int
api_yang_library_version(clicon_handle h,
void *req,
int pretty,
restconf_media media_out)
{
#if 1
clicon_debug(1, "%s", __FUNCTION__);
return 0;
#else
int retval = -1;
cxobj *xt = NULL;
cbuf *cb = NULL;
char *ietf_yang_library_revision = "2016-06-21"; /* XXX */
clicon_debug(1, "%s", __FUNCTION__);
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out));
FCGX_FPrintF(r->out, "\r\n");
if (clixon_xml_parse_va(YB_NONE, NULL, &xt, NULL,
"<yang-library-version>%s</yang-library-version>",
ietf_yang_library_revision) < 0)
goto done;
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
if ((cb = cbuf_new()) == NULL){
goto done;
}
switch (media_out){
case YANG_DATA_XML:
if (clicon_xml2cbuf(cb, xt, 0, pretty, -1) < 0)
goto done;
break;
case YANG_DATA_JSON:
if (xml2json_cbuf(cb, xt, pretty) < 0)
goto done;
break;
default:
break;
}
clicon_debug(1, "%s cb%s", __FUNCTION__, cbuf_get(cb));
FCGX_FPrintF(r->out, "%s\n", cb?cbuf_get(cb):"");
FCGX_FPrintF(r->out, "\n\n");
retval = 0;
done:
if (cb)
cbuf_free(cb);
if (xt)
xml_free(xt);
return retval;
#endif
}
/*! Generic REST method, GET, PUT, DELETE, etc
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_in Input media
* @param[in] media_out Output media
*/
static int
api_data(clicon_handle h,
void *req,
char *api_path,
cvec *pcvec,
int pi,
cvec *qvec,
char *data,
int pretty,
restconf_media media_out)
{
#if 1
clicon_debug(1, "%s", __FUNCTION__);
return 0;
#else
int retval = -1;
char *request_method;
clicon_debug(1, "%s", __FUNCTION__);
request_method = clixon_restconf_param_get(h, "REQUEST_METHOD");
clicon_debug(1, "%s method:%s", __FUNCTION__, request_method);
if (strcmp(request_method, "OPTIONS")==0)
retval = api_data_options(h, req);
else if (strcmp(request_method, "HEAD")==0)
retval = api_data_head(h, req, api_path, pcvec, pi, qvec, pretty, media_out);
else if (strcmp(request_method, "GET")==0)
retval = api_data_get(h, req, api_path, pcvec, pi, qvec, pretty, media_out);
else if (strcmp(request_method, "POST")==0)
retval = api_data_post(h, req, api_path, pi, qvec, data, pretty, media_out);
else if (strcmp(request_method, "PUT")==0)
retval = api_data_put(h, req, api_path, pcvec, pi, qvec, data, pretty, media_out);
else if (strcmp(request_method, "PATCH")==0)
retval = api_data_patch(h, req, api_path, pcvec, pi, qvec, data, pretty, media_out);
else if (strcmp(request_method, "DELETE")==0)
retval = api_data_delete(h, req, api_path, pi, pretty, media_out);
else
retval = restconf_notfound(h, req);
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval;
#endif
}
/*! Move back to restconf_methods_get
*/
static int
api_operations_get(clicon_handle h,
void *req,
char *path,
int pi,
cvec *qvec,
char *data,
int pretty,
restconf_media media_out)
{
int retval = -1;
yang_stmt *yspec;
yang_stmt *ymod; /* yang module */
yang_stmt *yc;
char *namespace;
cbuf *cb = NULL;
cxobj *xt = NULL;
int i;
clicon_debug(1, "%s", __FUNCTION__);
yspec = clicon_dbspec_yang(h);
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
switch (media_out){
case YANG_DATA_XML:
cprintf(cb, "<operations>");
break;
case YANG_DATA_JSON:
if (pretty)
cprintf(cb, "{\"operations\": {\n");
else
cprintf(cb, "{\"operations\":{");
break;
default:
break;
}
ymod = NULL;
i = 0;
while ((ymod = yn_each(yspec, ymod)) != NULL) {
namespace = yang_find_mynamespace(ymod);
yc = NULL;
while ((yc = yn_each(ymod, yc)) != NULL) {
if (yang_keyword_get(yc) != Y_RPC)
continue;
switch (media_out){
case YANG_DATA_XML:
cprintf(cb, "<%s xmlns=\"%s\"/>", yang_argument_get(yc), namespace);
break;
case YANG_DATA_JSON:
if (i++){
cprintf(cb, ",");
if (pretty)
cprintf(cb, "\n\t");
}
if (pretty)
cprintf(cb, "\"%s:%s\": [null]", yang_argument_get(ymod), yang_argument_get(yc));
else
cprintf(cb, "\"%s:%s\":[null]", yang_argument_get(ymod), yang_argument_get(yc));
break;
default:
break;
}
}
}
switch (media_out){
case YANG_DATA_XML:
cprintf(cb, "</operations>");
break;
case YANG_DATA_JSON:
if (pretty)
cprintf(cb, "}\n}");
else
cprintf(cb, "}}");
break;
default:
break;
}
if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0)
goto done;
if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)
goto done;
if (restconf_reply_send(req, 200, cb) < 0)
goto done;
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cb)
cbuf_free(cb);
if (xt)
xml_free(xt);
return retval;
}
/*! Operations REST method, POST
* @param[in] h CLIXON handle
* @param[in] req Generic Www handle (can be part of clixon handle)
* @param[in] request_method eg GET,...
* @param[in] path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* @param[in] media_out Output media
*/
static int
api_operations(clicon_handle h,
void *req,
char *request_method,
char *path,
cvec *pcvec,
int pi,
cvec *qvec,
char *data,
int pretty,
restconf_media media_out)
{
int retval;
clicon_debug(1, "%s", __FUNCTION__);
if (strcmp(request_method, "GET")==0)
retval = api_operations_get(h, req, path, pi, qvec, data, pretty, media_out);
#ifdef NYI
else if (strcmp(request_method, "POST")==0)
retval = api_operations_post(h, req, path, pi, qvec, data,
pretty, media_out);
#endif
else
retval = restconf_notfound(h, req);
return retval;
}
/*! Process a /restconf root input, this is the root of the restconf processing
* @param[in] h Clicon handle
* @param[in] req Generic Www handle (can be part of clixon handle)
* @param[in] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
*/
int
api_root_restconf(clicon_handle h,
void *req,
cvec *qvec)
{
int retval = -1;
char *request_method = NULL; /* GET,.. */
char *api_resource = NULL; /* RFC8040 3.3: eg data/operations */
char *path;
char **pvec = NULL;
cvec *pcvec = NULL; /* for rest api */
int pn;
int pretty;
cbuf *cb = NULL;
char *media_str = NULL;
restconf_media media_out = YANG_DATA_JSON;
char *indata = NULL;
int authenticated = 0;
cxobj *xret = NULL;
cxobj *xerr;
clicon_debug(1, "%s", __FUNCTION__);
if (req == NULL){
errno = EINVAL;
goto done;
}
request_method = clixon_restconf_param_get(h, "REQUEST_METHOD");
path = restconf_uripath(h);
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
/* Get media for output (proactive negotiation) RFC7231 by using
* Accept:. This is for methods that have output, such as GET,
* operation POST, etc
* If accept is * default is yang-json
*/
if ((media_str = clixon_restconf_param_get(h, "HTTP_ACCEPT")) == NULL){
// retval = restconf_unsupported_media(r);
// goto done;
}
else if ((int)(media_out = restconf_media_str2int(media_str)) == -1){
if (strcmp(media_str, "*/*") == 0) /* catch-all */
media_out = YANG_DATA_JSON;
else{
retval = restconf_unsupported_media(req);
goto done;
}
}
clicon_debug(1, "%s ACCEPT: %s %s", __FUNCTION__, media_str, restconf_media_int2str(media_out));
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
goto done;
/* Sanity check of path. Should be /restconf/ */
if (pn < 2){
restconf_notfound(h, req);
goto ok;
}
if (strlen(pvec[0]) != 0){
retval = restconf_notfound(h, req);
goto done;
}
if (strcmp(pvec[1], RESTCONF_API)){
retval = restconf_notfound(h, req);
goto done;
}
if (pn == 2){
retval = api_root(h, req, request_method, pretty, media_out);
goto done;
}
if ((api_resource = pvec[2]) == NULL){
retval = restconf_notfound(h, req);
goto done;
}
clicon_debug(1, "%s: api_resource=%s", __FUNCTION__, api_resource);
if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */
goto done;
/* data */
if ((cb = restconf_get_indata(req)) == NULL) /* XXX NYI ACTUALLY not always needed, do this later? */
goto done;
indata = cbuf_get(cb);
clicon_debug(1, "%s DATA=%s", __FUNCTION__, indata);
/* If present, check credentials. See "plugin_credentials" in plugin
* See RFC 8040 section 2.5
*/
if ((authenticated = clixon_plugin_auth_all(h, req)) < 0)
goto done;
clicon_debug(1, "%s auth:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
/* If set but no user, set a dummy user */
if (authenticated){
if (clicon_username_get(h) == NULL)
clicon_username_set(h, "none");
}
else{
if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0)
goto done;
if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){
if (api_return_err(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
goto ok;
}
clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
if (strcmp(api_resource, "yang-library-version")==0){
if (api_yang_library_version(h, req, pretty, media_out) < 0) /* XXX NYI */
goto done;
}
else if (strcmp(api_resource, "data") == 0){ /* restconf, skip /api/data */ /* XXX NYI */
if (api_data(h, req, path, pcvec, 2, qvec, indata,
pretty, media_out) < 0)
goto done;
}
else if (strcmp(api_resource, "operations") == 0){ /* rpc */
if (api_operations(h, req, request_method, path, pcvec, 2, qvec, indata,
pretty, media_out) < 0)
goto done;
}
else
restconf_notfound(h, req);
ok:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (pcvec)
cvec_free(pcvec);
if (pvec)
free(pvec);
if (cb)
cbuf_free(cb);
if (xret)
xml_free(xret);
return retval;
}

View file

@ -53,5 +53,6 @@
* Prototypes * Prototypes
*/ */
int api_well_known(clicon_handle h, void *req); int api_well_known(clicon_handle h, void *req);
int api_root_restconf(clicon_handle h, void *req, cvec *qvec);
#endif /* _RESTCONF_ROOT_H_ */ #endif /* _RESTCONF_ROOT_H_ */

View file

@ -86,7 +86,8 @@
#include <fcgiapp.h> /* Need to be after clixon_xml.h due to attribute format */ #include <fcgiapp.h> /* Need to be after clixon_xml.h due to attribute format */
#include "restconf_lib.h" #include "restconf_lib.h"
#include "restconf_fcgi_lib.h" #include "restconf_api.h"
#include "restconf_err.h"
#include "restconf_stream.h" #include "restconf_stream.h"
/* /*
@ -377,7 +378,6 @@ api_stream(clicon_handle h,
path = restconf_uripath(h); path = restconf_uripath(h);
query = clixon_restconf_param_get(h, "QUERY_STRING"); query = clixon_restconf_param_get(h, "QUERY_STRING");
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
restconf_test(r, 1);
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL) if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
goto done; goto done;
/* Sanity check of path. Should be /stream/<name> */ /* Sanity check of path. Should be /stream/<name> */
@ -404,7 +404,7 @@ api_stream(clicon_handle h,
if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */ if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */
goto done; goto done;
/* data */ /* data */
if ((cb = readdata(r)) == NULL) if ((cb = restconf_get_indata(r)) == NULL)
goto done; goto done;
data = cbuf_get(cb); data = cbuf_get(cb);
clicon_debug(1, "%s DATA=%s", __FUNCTION__, data); clicon_debug(1, "%s DATA=%s", __FUNCTION__, data);

View file

@ -3,7 +3,7 @@
# Create working dir as variable "dir" # Create working dir as variable "dir"
# The functions are somewhat wildgrown, a little too many: # The functions are somewhat wildgrown, a little too many:
# - expectfn # - expectfn
# - expecteq # - expectpart
# - expecteof # - expecteof
# - expecteofeq # - expecteofeq
# - expecteofx # - expecteofx
@ -286,7 +286,7 @@ new(){
# Example: expectfn "$clixon_cli -1 -f $cfg show conf cli" 0 "line1" "line2" # Example: expectfn "$clixon_cli -1 -f $cfg show conf cli" 0 "line1" "line2"
# XXX: for some reason some curl commands dont work here, eg # XXX: for some reason some curl commands dont work here, eg
# curl -H 'Accept: application/xrd+xml' # curl -H 'Accept: application/xrd+xml'
# instead use expectpart # NOTE: Please us expectpart instead!!
expectfn(){ expectfn(){
cmd=$1 cmd=$1
retval=$2 retval=$2
@ -336,34 +336,6 @@ expectfn(){
} }
# Evaluate and return # Evaluate and return
# Example: expecteq $(fn arg) 0 "my return"
# - evaluated expression
# - expected command return value (0 if OK)
# - expected stdout outcome
expecteq(){
r=$?
ret=$1
retval=$2
expect=$3
# echo "r:$r"
# echo "ret:\"$ret\""
# echo "retval:$retval"
# echo "expect:$expect"
if [ $r != $retval ]; then
echo -e "\e[31m\nError ($r != $retval) in Test$testnr [$testname]:"
echo -e "\e[0m:"
exit -1
fi
if [ -z "$ret" -a -z "$expect" ]; then
return
fi
if [[ "$ret" != "$expect" ]]; then
err "$expect" "$ret"
fi
}
# Evaluate and return
# like expecteq but partial match is OK
# Example: expectpart $(fn arg) 0 "my return" -- "foo" # Example: expectpart $(fn arg) 0 "my return" -- "foo"
# - evaluated expression # - evaluated expression
# - expected command return value (0 if OK) # - expected command return value (0 if OK)

View file

@ -207,7 +207,7 @@ new "api-path double string key k1=a$rnd, - empty k2 string"
expectpart "$($clixon_util_path -f $xml3 -y $ydir -p /moda:x3/y=a1,)" 0 "0: <y><k1>a1</k1><k2/><z>foo1</z></y>" expectpart "$($clixon_util_path -f $xml3 -y $ydir -p /moda:x3/y=a1,)" 0 "0: <y><k1>a1</k1><k2/><z>foo1</z></y>"
new "api-path double string key k1=a$rnd, - no k2 string - three matches" new "api-path double string key k1=a$rnd, - no k2 string - three matches"
expecteq "$($clixon_util_path -f $xml3 -y $ydir -p /moda:x3/y=a1)" 0 "0: <y><k1>a1</k1><k2/><z>foo1</z></y> expectpart "$($clixon_util_path -f $xml3 -y $ydir -p /moda:x3/y=a1)" 0 "0: <y><k1>a1</k1><k2/><z>foo1</z></y>
1: <y><k1>a1</k1><k2>a1</k2><z>foo1</z></y> 1: <y><k1>a1</k1><k2>a1</k2><z>foo1</z></y>
2: <y><k1>a1</k1><k2>b1</k2><z>foob1</z></y>" 2: <y><k1>a1</k1><k2>b1</k2><z>foob1</z></y>"

View file

@ -170,20 +170,19 @@ expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply
#-- restconf #-- restconf
new "restconf DELETE whole datastore" new "restconf DELETE whole datastore"
expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 "" expectpart "$(curl -sik -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content"
new "restconf set protocol tcp+udp fail" new "restconf set protocol tcp+udp fail"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/system:system/protocol -d '{"system:protocol":{"tcp": [null], "udp": [null]}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"udp"},"error-severity":"error","error-message":"Element in choice statement already exists"}}} ' expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/system:system/protocol -d '{"system:protocol":{"tcp": [null], "udp": [null]}}')" 0 "HTTP/1.1 400 Bad Request" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"udp"},"error-severity":"error","error-message":"Element in choice statement already exists"}}}'
new "restconf set protocol tcp" new "restconf set protocol tcp"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/system:system/protocol -d {\"system:protocol\":{\"tcp\":[null]}})" 0 "" expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/system:system/protocol -d {\"system:protocol\":{\"tcp\":[null]}})" 0 "HTTP/1.1 201 Created"
new "restconf get protocol tcp" new "restconf get protocol tcp"
expecteq "$(curl -s -X GET http://localhost/restconf/data/system:system)" 0 '{"system:system":{"protocol":{"tcp":[null]}}} expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/system:system)" 0 "HTTP/1.1 200 OK" '{"system:system":{"protocol":{"tcp":\[null\]}}}'
'
new "restconf set protocol tcp+udp fail again" new "restconf set protocol tcp+udp fail again"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/system:system/protocol -d '{"system:protocol":{"tcp": [null], "udp": [null]}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"udp"},"error-severity":"error","error-message":"Element in choice statement already exists"}}} ' expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/system:system/protocol -d '{"system:protocol":{"tcp": [null], "udp": [null]}}')" 0 "HTTP/1.1 400 Bad Request" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"udp"},"error-severity":"error","error-message":"Element in choice statement already exists"}}}'
new "cli set protocol udp" new "cli set protocol udp"
expectfn "$clixon_cli -1 -f $cfg -l o set system protocol udp" 0 "^$" expectfn "$clixon_cli -1 -f $cfg -l o set system protocol udp" 0 "^$"

View file

@ -239,7 +239,7 @@ for (( ii=0; ii<$rep; ii++ )); do
expectpart "$($clixon_util_path -f $xml3 -y $ydir -p /a:x3/a:y[k1=\"a1\"][k2=\"\"])" 0 "0: <y><k1>a1</k1><k2/><z>foo1</z></y>" expectpart "$($clixon_util_path -f $xml3 -y $ydir -p /a:x3/a:y[k1=\"a1\"][k2=\"\"])" 0 "0: <y><k1>a1</k1><k2/><z>foo1</z></y>"
new "instance-id double string key k1=a$rnd, - no k2 string - three matches" new "instance-id double string key k1=a$rnd, - no k2 string - three matches"
expecteq "$($clixon_util_path -f $xml3 -y $ydir -p /a:x3/a:y[k1=\"a1\"])" 0 "0: <y><k1>a1</k1><k2/><z>foo1</z></y> expectpart "$($clixon_util_path -f $xml3 -y $ydir -p /a:x3/a:y[k1=\"a1\"])" 0 "0: <y><k1>a1</k1><k2/><z>foo1</z></y>
1: <y><k1>a1</k1><k2>a1</k2><z>foo1</z></y> 1: <y><k1>a1</k1><k2>a1</k2><z>foo1</z></y>
2: <y><k1>a1</k1><k2>b1</k2><z>foob1</z></y>" 2: <y><k1>a1</k1><k2>b1</k2><z>foob1</z></y>"

View file

@ -137,11 +137,11 @@ if [ $RC -ne 0 ]; then
fi fi
new "auth get" new "auth get"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"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 -sik -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"}}}'
# explicitly disable nacm (regression on netgate bug) # explicitly disable nacm (regression on netgate bug)
new "disable nacm" new "disable nacm"
expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": false}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "" expectpart "$(curl -u andy:bar -sik -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"
new "auth set authentication config" new "auth set authentication config"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config>$RULES</config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config>$RULES</config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
@ -150,39 +150,36 @@ new "commit it"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "auth get (no user: access denied)" new "auth get (no user: access denied)"
expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}} ' expectpart "$(curl -sik -X GET -H \"Accept:\ application/yang-data+json\" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 403 Forbidden" '{"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)" new "auth get (wrong passwd: access denied)"
expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" 0 '{"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 -sik -X GET $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 403 Forbidden" '{"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)" new "auth get (access)"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0} expectpart "$(curl -u andy:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}'
'
#----------------Enable NACM #----------------Enable NACM
new "enable nacm" new "enable nacm"
expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "" expectpart "$(curl -u andy:bar -sik -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"
new "admin get nacm" new "admin get nacm"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0} expectpart "$(curl -u andy:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}'
'
new "limited get nacm" new "limited get nacm"
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0} expectpart "$(curl -u wilma:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}'
'
new "guest get nacm" new "guest get nacm"
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}} ' expectpart "$(curl -u guest:bar -sik -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"}}}'
new "admin edit nacm" new "admin edit nacm"
expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x":1}' http://localhost/restconf/data/nacm-example:x)" 0 "" expectpart "$(curl -u andy:bar -sik -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"
new "limited edit nacm" new "limited edit nacm"
expecteq "$(curl -u wilma:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 2}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}} ' expectpart "$(curl -u wilma:bar -sik -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"}}}'
new "guest edit nacm" new "guest edit nacm"
expecteq "$(curl -u guest:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 3}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}} ' expectpart "$(curl -u guest:bar -sik -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"}}}'
if [ $RC -ne 0 ]; then if [ $RC -ne 0 ]; then
new "Kill restconf daemon" new "Kill restconf daemon"

View file

@ -135,50 +135,57 @@ EOF
# Use POST (instead of startup) # Use POST (instead of startup)
if [ $db = init ]; then if [ $db = init ]; then
new "Set Initial data using POST" new "Set Initial data using POST"
expectpart "$(curl -u guest:bar -siS -X POST -H "Content-Type: application/yang-data+xml" -d "$XML" http://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" expectpart "$(curl -u guest:bar -sik -X POST -H "Content-Type: application/yang-data+xml" -d "$XML" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created"
new "Set NACM using POST" new "Set NACM using POST"
expectpart "$(curl -u guest:bar -siS -X POST -H "Content-Type: application/yang-data+xml" -d "$NACM" http://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" expectpart "$(curl -u guest:bar -sik -X POST -H "Content-Type: application/yang-data+xml" -d "$NACM" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created"
fi fi
#----------- First get #----------- First get
case "$ret1" in case "$ret1" in
0) ret='{"nacm-example:x":42} 0) ret='{"nacm-example:x":42}'
' status="HTTP/1.1 200 OK"
;; ;;
1) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' 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"
;; ;;
2) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' 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"
;; ;;
esac esac
new "get startup 42" new "get startup 42"
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 "$ret" expectpart "$(curl -u guest:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "$status" "$ret"
#----------- Then edit #----------- Then edit
case "$ret2" in case "$ret2" in
0) ret='' 0) ret=''
status="HTTP/1.1 204 No Content"
;; ;;
1) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' 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"
;; ;;
esac esac
new "edit new 99" new "edit new 99"
expecteq "$(curl -u guest:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 99}' http://localhost/restconf/data/nacm-example:x)" 0 "$ret" expectpart "$(curl -u guest:bar -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 99}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "$status" "$ret"
#----------- Then second get #----------- Then second get
case "$ret3" in case "$ret3" in
0) ret='{"nacm-example:x":99} 0) ret='{"nacm-example:x":99}'
' status="HTTP/1.1 200 OK"
;; ;;
1) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' 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"
;; ;;
2) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' 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"
;;
3) ret='{"nacm-example:x":42}'
status="HTTP/1.1 200 OK"
;; ;;
3) ret='{"nacm-example:x":42}
'
esac esac
new "get 99" new "get 99"
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 "$ret" expectpart "$(curl -u guest:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "$status" "$ret"
new "Kill restconf daemon" new "Kill restconf daemon"
stop_restconf stop_restconf

View file

@ -157,40 +157,37 @@ if [ $RC -ne 0 ]; then
fi fi
new "auth get" new "auth get"
expectpart "$(curl -u andy:bar -siS -X GET http://localhost/restconf/data)" 0 'HTTP/1.1 200 OK' '{"data":{"clixon-example:state":{"op":\["41","42","43"\]}' expectpart "$(curl -u andy:bar -sik -X GET $RCPROTO://localhost/restconf/data)" 0 'HTTP/1.1 200 OK' '{"data":{"clixon-example:state":{"op":\["41","42","43"\]}'
new "Set x to 0" new "Set x to 0"
expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 0}' http://localhost/restconf/data/nacm-example:x)" 0 "" expectpart "$(curl -u andy:bar -sik -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"
new "auth get (no user: access denied)" new "auth get (no user: access denied)"
expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}} ' expectpart "$(curl -sik -X GET -H \"Accept:\ application/yang-data+json\" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 403 Forbidden" '{"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)" new "auth get (wrong passwd: access denied)"
expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" 0 '{"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 -sik -X GET $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 403 Forbidden" '{"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)" new "auth get (access)"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0} expectpart "$(curl -u andy:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}'
'
new "admin get nacm" new "admin get nacm"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0} expectpart "$(curl -u andy:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}'
'
new "limited get nacm" new "limited get nacm"
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0} expectpart "$(curl -u wilma:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}'
'
new "guest get nacm" new "guest get nacm"
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}} ' expectpart "$(curl -u guest:bar -sik -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"}}}'
new "admin edit nacm" new "admin edit nacm"
expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 1}' http://localhost/restconf/data/nacm-example:x)" 0 "" expectpart "$(curl -u andy:bar -sik -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"
new "limited edit nacm" new "limited edit nacm"
expecteq "$(curl -u wilma:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 2}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}} ' expectpart "$(curl -u wilma:bar -sik -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"}}}'
new "guest edit nacm" new "guest edit nacm"
expecteq "$(curl -u guest:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 3}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}} ' expectpart "$(curl -u guest:bar -sik -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"}}}'
new "cli show conf as admin" new "cli show conf as admin"
expectfn "$clixon_cli -1 -U andy -l o -f $cfg show conf" 0 "^x 1;$" expectfn "$clixon_cli -1 -U andy -l o -f $cfg show conf" 0 "^x 1;$"

View file

@ -164,13 +164,12 @@ new "commit it"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "enable nacm" new "enable nacm"
expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "" expectpart "$(curl -u andy:bar -sik -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"
#--------------- nacm enabled #--------------- nacm enabled
new "admin get nacm" new "admin get nacm"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0} expectpart "$(curl -u andy:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}'
'
# Rule 1: deny-kill-session # Rule 1: deny-kill-session
new "deny-kill-session: limited fail (netconf)" new "deny-kill-session: limited fail (netconf)"
@ -187,17 +186,17 @@ new "deny-delete-config: limited fail (netconf)"
expecteof "$clixon_netconf -qf $cfg -U wilma" 0 "<rpc><delete-config><target><startup/></target></delete-config></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>access-denied</error-tag><error-severity>error</error-severity><error-message>access denied</error-message></rpc-error></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg -U wilma" 0 "<rpc><delete-config><target><startup/></target></delete-config></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>access-denied</error-tag><error-severity>error</error-severity><error-message>access denied</error-message></rpc-error></rpc-reply>]]>]]>$"
new "deny-delete-config: guest fail (restconf)" new "deny-delete-config: guest fail (restconf)"
expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}} ' expectpart "$(curl -u guest:bar -sik -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"}}}'
# In restconf delete-config is translated to edit-config which is permitted # In restconf delete-config is translated to edit-config which is permitted
new "deny-delete-config: limited fail (restconf) ok" new "deny-delete-config: limited fail (restconf) ok"
expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data)" 0 '' expectpart "$(curl -u wilma:bar -sik -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content"
new "admin get nacm (should fail)" new "admin get nacm (should fail)"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"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 -sik -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"}}}'
new "deny-delete-config: admin ok (restconf)" new "deny-delete-config: admin ok (restconf)"
expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" 0 '' expectpart "$(curl -u andy:bar -sik -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content"
# Here the whole config is gone so we need to start again # Here the whole config is gone so we need to start again
new "auth set authentication config (restart)" new "auth set authentication config (restart)"
@ -207,14 +206,14 @@ new "commit it"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "enable nacm" new "enable nacm"
expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "" expectpart "$(curl -u andy:bar -sik -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"
# Rule 3: permit-edit-config # Rule 3: permit-edit-config
new "permit-edit-config: limited ok restconf" new "permit-edit-config: limited ok restconf"
expecteq "$(curl -u wilma:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x":2}' http://localhost/restconf/data/nacm-example:x)" 0 '' expectpart "$(curl -u wilma:bar -sik -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"
new "permit-edit-config: guest fail restconf" new "permit-edit-config: guest fail restconf"
expecteq "$(curl -u guest:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x":2}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}} ' expectpart "$(curl -u guest:bar -sik -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"}}}'
new "Kill restconf daemon" new "Kill restconf daemon"
stop_restconf stop_restconf

View file

@ -147,8 +147,7 @@ done | $clixon_netconf -qf $cfg > /dev/null; } 2>&1 | awk '/real/ {print $2}'
# RESTCONF get # RESTCONF get
new "restconf get test single req" new "restconf get test single req"
expecteq "$(curl -s -X GET http://localhost/restconf/data/example:interfaces/a=foo/b/interface=e1)" 0 '{"example:interface":[{"name":"e1","type":"ex:eth","enabled":true,"status":"up"}]} expectpart "$(curl -sik -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"}\]}'
'
new "restconf get $perfreq single reqs" new "restconf get $perfreq single reqs"
#echo "curl -sG http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e0" #echo "curl -sG http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e0"

View file

@ -139,8 +139,7 @@ done | $clixon_netconf -qf $cfg > /dev/null; } 2>&1 | awk '/real/ {print $2}'
# RESTCONF get # RESTCONF get
#echo "curl -s -X GET http://localhost/restconf/data/example:interfaces/a=foo/b/interface=e1" #echo "curl -s -X GET http://localhost/restconf/data/example:interfaces/a=foo/b/interface=e1"
new "restconf get test single req" new "restconf get test single req"
time -p expecteq "$(curl -s -X GET http://localhost/restconf/data/example:interfaces/a=foo/b/interface=e1)" 0 '{"example:interface":[{"name":"e1","type":"ex:eth","status":"up"}]} time -p expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:interfaces/a=foo/b/interface=e1)" 0 '{"example:interface":[{"name":"e1","type":"ex:eth","status":"up"}]}' | awk '/real/ {print $2}'
' | awk '/real/ {print $2}'
new "restconf get $perfreq single reqs" new "restconf get $perfreq single reqs"
#echo "curl -sG http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e0" #echo "curl -sG http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e0"

View file

@ -65,18 +65,18 @@ new "restconf root discovery. RFC 8040 3.1 (xml+xrd)"
expectpart "$(curl -sik -X GET $RCPROTO://localhost/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>" expectpart "$(curl -sik -X GET $RCPROTO://localhost/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>"
new "restconf get restconf resource. RFC 8040 3.3 (json)" new "restconf get restconf resource. RFC 8040 3.3 (json)"
expectpart "$(curl -si -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf)" 0 'HTTP/1.1 200 OK' '{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2016-06-21"}}' expectpart "$(curl -sik -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf)" 0 'HTTP/1.1 200 OK' '{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2016-06-21"}}'
new "restconf get restconf resource. RFC 8040 3.3 (xml)" new "restconf get restconf resource. RFC 8040 3.3 (xml)"
# Get XML instead of JSON? # Get XML instead of JSON?
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf)" 0 'HTTP/1.1 200 OK' '<restconf xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><data/><operations/><yang-library-version>2016-06-21</yang-library-version></restconf>' expectpart "$(curl -sik -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf)" 0 'HTTP/1.1 200 OK' '<restconf xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><data/><operations/><yang-library-version>2016-06-21</yang-library-version></restconf>'
# Should be alphabetically ordered # Should be alphabetically ordered
new "restconf get restconf/operations. RFC8040 3.3.2 (json)" new "restconf get restconf/operations. RFC8040 3.3.2 (json)"
expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/operations)" 0 'HTTP/1.1 200 OK' '{"operations":{"clixon-example:client-rpc":\[null\],"clixon-example:empty":\[null\],"clixon-example:optional":\[null\],"clixon-example:example":\[null\],"clixon-lib:debug":\[null\],"clixon-lib:ping":\[null\],"clixon-lib:stats":\[null\],"clixon-lib:restart-plugin":\[null\],"ietf-netconf:get-config":\[null\],"ietf-netconf:edit-config":\[null\],"ietf-netconf:copy-config":\[null\],"ietf-netconf:delete-config":\[null\],"ietf-netconf:lock":\[null\],"ietf-netconf:unlock":\[null\],"ietf-netconf:get":\[null\],"ietf-netconf:close-session":\[null\],"ietf-netconf:kill-session":\[null\],"ietf-netconf:commit":\[null\],"ietf-netconf:discard-changes":\[null\],"ietf-netconf:validate":\[null\]}}' expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/operations)" 0 'HTTP/1.1 200 OK' '{"operations":{"clixon-example:client-rpc":\[null\],"clixon-example:empty":\[null\],"clixon-example:optional":\[null\],"clixon-example:example":\[null\],"clixon-lib:debug":\[null\],"clixon-lib:ping":\[null\],"clixon-lib:stats":\[null\],"clixon-lib:restart-plugin":\[null\],"ietf-netconf:get-config":\[null\],"ietf-netconf:edit-config":\[null\],"ietf-netconf:copy-config":\[null\],"ietf-netconf:delete-config":\[null\],"ietf-netconf:lock":\[null\],"ietf-netconf:unlock":\[null\],"ietf-netconf:get":\[null\],"ietf-netconf:close-session":\[null\],"ietf-netconf:kill-session":\[null\],"ietf-netconf:commit":\[null\],"ietf-netconf:discard-changes":\[null\],"ietf-netconf:validate":\[null\]}}'
new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" new "restconf get restconf/operations. RFC8040 3.3.2 (xml)"
ret=$(curl -s -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/operations) ret=$(curl -sik -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/operations)
expect='<operations><client-rpc xmlns="urn:example:clixon"/><empty xmlns="urn:example:clixon"/><optional xmlns="urn:example:clixon"/><example xmlns="urn:example:clixon"/><debug xmlns="http://clicon.org/lib"/><ping xmlns="http://clicon.org/lib"/><stats xmlns="http://clicon.org/lib"/><restart-plugin xmlns="http://clicon.org/lib"/><get-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><copy-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><delete-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><lock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><unlock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><close-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><kill-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><commit xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><discard-changes xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><validate xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/></operations>' expect='<operations><client-rpc xmlns="urn:example:clixon"/><empty xmlns="urn:example:clixon"/><optional xmlns="urn:example:clixon"/><example xmlns="urn:example:clixon"/><debug xmlns="http://clicon.org/lib"/><ping xmlns="http://clicon.org/lib"/><stats xmlns="http://clicon.org/lib"/><restart-plugin xmlns="http://clicon.org/lib"/><get-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><copy-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><delete-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><lock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><unlock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><close-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><kill-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><commit xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><discard-changes xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><validate xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/></operations>'
match=`echo $ret | grep --null -Eo "$expect"` match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
@ -84,10 +84,10 @@ if [ -z "$match" ]; then
fi fi
new "restconf get restconf/yang-library-version. RFC8040 3.3.3" new "restconf get restconf/yang-library-version. RFC8040 3.3.3"
expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/yang-library-version)" 0 'HTTP/1.1 200 OK' '{"yang-library-version":"2016-06-21"}' expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/yang-library-version)" 0 'HTTP/1.1 200 OK' '{"yang-library-version":"2016-06-21"}'
new "restconf get restconf/yang-library-version. RFC8040 3.3.3 (xml)" new "restconf get restconf/yang-library-version. RFC8040 3.3.3 (xml)"
ret=$(curl -s -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/yang-library-version) ret=$(curl -sik -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/yang-library-version)
expect="<yang-library-version>2016-06-21</yang-library-version>" expect="<yang-library-version>2016-06-21</yang-library-version>"
match=`echo $ret | grep --null -Eo "$expect"` match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
@ -95,46 +95,45 @@ if [ -z "$match" ]; then
fi fi
new "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895 (explicit resource)" new "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895 (explicit resource)"
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-yang-library:modules-state/module=ietf-interfaces,2018-02-20)" 0 'HTTP/1.1 200 OK' '{"ietf-yang-library:module":\[{"name":"ietf-interfaces","revision":"2018-02-20","namespace":"urn:ietf:params:xml:ns:yang:ietf-interfaces","conformance-type":"implement"}\]}' expectpart "$(curl -sik -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-yang-library:modules-state/module=ietf-interfaces,2018-02-20)" 0 'HTTP/1.1 200 OK' '{"ietf-yang-library:module":\[{"name":"ietf-interfaces","revision":"2018-02-20","namespace":"urn:ietf:params:xml:ns:yang:ietf-interfaces","conformance-type":"implement"}\]}'
new "restconf options. RFC 8040 4.1" new "restconf options. RFC 8040 4.1"
expectpart "$(curl -is -X OPTIONS $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE" expectpart "$(curl -is -X OPTIONS $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE"
# -I means HEAD # -I means HEAD
new "restconf HEAD. RFC 8040 4.2" new "restconf HEAD. RFC 8040 4.2"
expectpart "$(curl -si -I -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+json" expectpart "$(curl -sik -I -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+json"
new "restconf empty rpc JSON" new "restconf empty rpc JSON"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content"
new "restconf empty rpc XML" new "restconf empty rpc XML"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+xml" -d '<input xmlns="urn:example:clixon"></input>' $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+xml" -d '<input xmlns="urn:example:clixon"></input>' $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content"
new "restconf empty rpc, default media type should fail" new "restconf empty rpc, default media type should fail"
expectpart "$(curl -si -X POST -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type' expectpart "$(curl -sik -X POST -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type'
new "restconf empty rpc, default media type should fail (JSON)" new "restconf empty rpc, default media type should fail (JSON)"
expectpart "$(curl -si -X POST -H "Accept: application/yang-data+json" -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type' expectpart "$(curl -sik -X POST -H "Accept: application/yang-data+json" -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type'
new "restconf empty rpc with extra args (should fail)" new "restconf empty rpc with extra args (should fail)"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":{\"extra\":null}} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"Unrecognized parameter: extra in rpc: empty"}}}' expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":{\"extra\":null}} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"Unrecognized parameter: extra in rpc: empty"}}}'
# Irritiating to get debugs on the terminal # Irritiating to get debugs on the terminal
#new "restconf debug rpc" #new "restconf debug rpc"
#expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-lib:input\":{\"level\":0}} $RCPROTO://localhost/restconf/operations/clixon-lib:debug)" 0 "HTTP/1.1 204 No Content" #expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-lib:input\":{\"level\":0}} $RCPROTO://localhost/restconf/operations/clixon-lib:debug)" 0 "HTTP/1.1 204 No Content"
new "restconf get empty config + state json" new "restconf get empty config + state json"
expecteq "$(curl -sS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["41","42","43"]}} expectpart "$(curl -kisS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" '{"clixon-example:state":{"op":\["41","42","43"\]}}'
'
new "restconf get empty config + state json with wrong module name" new "restconf get empty config + state json with wrong module name"
expectpart "$(curl -siSG $RCPROTO://localhost/restconf/data/badmodule:state)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"badmodule"},"error-severity":"error","error-message":"No such yang module prefix"}}}' expectpart "$(curl -sikS -X GET $RCPROTO://localhost/restconf/data/badmodule:state)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"badmodule"},"error-severity":"error","error-message":"No such yang module prefix"}}}'
#'HTTP/1.1 404 Not Found' #'HTTP/1.1 404 Not Found'
#'{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"No such yang module: badmodule"}}}' #'{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"No such yang module: badmodule"}}}'
new "restconf get empty config + state xml" new "restconf get empty config + state xml"
ret=$(curl -s -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state) ret=$(curl -sik -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)
expect='<state xmlns="urn:example:clixon"><op>41</op><op>42</op><op>43</op></state>' expect='<state xmlns="urn:example:clixon"><op>41</op><op>42</op><op>43</op></state>'
match=`echo $ret | grep --null -Eo "$expect"` match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
@ -142,12 +141,11 @@ if [ -z "$match" ]; then
fi fi
new "restconf get data type json" new "restconf get data type json"
expecteq "$(curl -s -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"} expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"}'
'
new "restconf get state operation" new "restconf get state operation"
# Cant get shell macros to work, inline matching from lib.sh # Cant get shell macros to work, inline matching from lib.sh
ret=$(curl -s -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42) ret=$(curl -sik -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)
expect='<op xmlns="urn:example:clixon">42</op>' expect='<op xmlns="urn:example:clixon">42</op>'
match=`echo $ret | grep --null -Eo "$expect"` match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
@ -155,12 +153,11 @@ if [ -z "$match" ]; then
fi fi
new "restconf get state operation type json" new "restconf get state operation type json"
expecteq "$(curl -s -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"} expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"}'
'
new "restconf get state operation type xml" new "restconf get state operation type xml"
# Cant get shell macros to work, inline matching from lib.sh # Cant get shell macros to work, inline matching from lib.sh
ret=$(curl -s -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42) ret=$(curl -sik -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)
expect='<op xmlns="urn:example:clixon">42</op>' expect='<op xmlns="urn:example:clixon">42</op>'
match=`echo $ret | grep --null -Eo "$expect"` match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
@ -168,78 +165,72 @@ if [ -z "$match" ]; then
fi fi
new "restconf GET datastore" new "restconf GET datastore"
expecteq "$(curl -s -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["41","42","43"]}} expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" '{"clixon-example:state":{"op":\["41","42","43"\]}}'
'
# Exact match # Exact match
new "restconf Add subtree eth/0/0 to datastore using POST" new "restconf Add subtree eth/0/0 to datastore using POST"
expectpart "$(curl -s -i -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $RCPROTO://localhost/restconf/data)" 0 'HTTP/1.1 201 Created' 'Location: http://localhost/restconf/data/ietf-interfaces:interfaces' expectpart "$(curl -sik -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $RCPROTO://localhost/restconf/data)" 0 'HTTP/1.1 201 Created' 'Location: http://localhost/restconf/data/ietf-interfaces:interfaces'
new "restconf Re-add subtree eth/0/0 which should give error" new "restconf Re-add subtree eth/0/0 which should give error"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $RCPROTO://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}' expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $RCPROTO://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}'
new "restconf Check interfaces eth/0/0 added" new "restconf Check interfaces eth/0/0 added"
expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces" 0 '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up"}\]}}} expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
'
new "restconf delete interfaces" new "restconf delete interfaces"
expectpart "$(curl -si -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -sik -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 204 No Content"
new "restconf Check empty config" new "restconf Check empty config"
expectfn "curl -sG $RCPROTO://localhost/restconf/data/clixon-example:state" 0 "$state expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" "$state"
"
new "restconf Add interfaces subtree eth/0/0 using POST" new "restconf Add interfaces subtree eth/0/0 using POST"
expectpart "$(curl -si -X POST $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}')" 0 "HTTP/1.1 201 Created" expectpart "$(curl -sik -X POST $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}')" 0 "HTTP/1.1 201 Created"
new "restconf Check eth/0/0 added config" new "restconf Check eth/0/0 added config"
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}' expectpart "$(curl -sik -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
new "restconf Check eth/0/0 GET augmented state level 1" new "restconf Check eth/0/0 GET augmented state level 1"
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}' expectpart "$(curl -sik -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}'
new "restconf Check eth/0/0 GET augmented state level 2" new "restconf Check eth/0/0 GET augmented state level 2"
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0/clixon-example:my-status)" 0 'HTTP/1.1 200 OK' '{"clixon-example:my-status":{"int":42,"str":"foo"}}' expectpart "$(curl -sik -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0/clixon-example:my-status)" 0 'HTTP/1.1 200 OK' '{"clixon-example:my-status":{"int":42,"str":"foo"}}'
new "restconf Check eth/0/0 added state XXXXXXX" new "restconf Check eth/0/0 added state XXXXXXX"
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 'HTTP/1.1 200 OK' '{"clixon-example:state":{"op":\["41","42","43"\]}}' expectpart "$(curl -sik -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 'HTTP/1.1 200 OK' '{"clixon-example:state":{"op":\["41","42","43"\]}}'
new "restconf Re-post eth/0/0 which should generate error" new "restconf Re-post eth/0/0 which should generate error"
expectpart "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}} ' expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}} '
new "Add leaf description using POST" new "Add leaf description using POST"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:description":"The-first-interface"}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created" expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:description":"The-first-interface"}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created"
new "Add nothing using POST (expect fail)" new "Add nothing using POST (expect fail)"
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"The message-body MUST contain exactly one instance of the expected data resource"}}}' expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"The message-body MUST contain exactly one instance of the expected data resource"}}}'
new "restconf Check description added" new "restconf Check description added"
expecteq "$(curl -s -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","description":"The-first-interface","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}]}} expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","description":"The-first-interface","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
'
new "restconf delete eth/0/0" new "restconf delete eth/0/0"
expectpart "$(curl -si -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -sik -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 204 No Content"
new "Check deleted eth/0/0" new "Check deleted eth/0/0"
expectfn "curl -s -X GET http://localhost/restconf/data" 0 "$state" expectpart "$(curl -sik -X GET http://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "$state"
new "restconf Re-Delete eth/0/0 using none should generate error" new "restconf Re-Delete eth/0/0 using none should generate error"
expecteq "$(curl -s -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-missing","error-severity":"error","error-message":"Data does not exist; cannot delete resource"}}} ' expectpart "$(curl -sik -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 409 Conflict" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-missing","error-severity":"error","error-message":"Data does not exist; cannot delete resource"}}}'
new "restconf Add subtree eth/0/0 using PUT" new "restconf Add subtree eth/0/0 using PUT"
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created" expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created"
new "restconf get subtree" new "restconf get subtree"
expecteq "$(curl -s -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}]}} expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
'
new "restconf rpc using POST json" new "restconf rpc using POST json"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"42","y":"42"}} expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 "HTTP/1.1 200 OK" '{"clixon-example:output":{"x":"42","y":"42"}}'
'
if ! $YANG_UNKNOWN_ANYDATA ; then if ! $YANG_UNKNOWN_ANYDATA ; then
new "restconf rpc using POST json wrong" new "restconf rpc using POST json wrong"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: wrongelement with parent: example in namespace: urn:example:clixon"}}}' expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: wrongelement with parent: example in namespace: urn:example:clixon"}}}'
fi fi
new "restconf rpc non-existing rpc without namespace" new "restconf rpc non-existing rpc without namespace"
@ -255,7 +246,7 @@ new "restconf rpc missing input"
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"restconf RPC does not have input statement"}}}' expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"restconf RPC does not have input statement"}}}'
new "restconf rpc using POST xml" new "restconf rpc using POST xml"
ret=$(curl -s -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":42}}' $RCPROTO://localhost/restconf/operations/clixon-example:example) ret=$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":42}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)
expect='<output xmlns="urn:example:clixon"><x>42</x><y>42</y></output>' expect='<output xmlns="urn:example:clixon"><x>42</x><y>42</y></output>'
match=`echo $ret | grep --null -Eo "$expect"` match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
@ -263,10 +254,10 @@ if [ -z "$match" ]; then
fi fi
new "restconf rpc using wrong prefix" new "restconf rpc using wrong prefix"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' $RCPROTO://localhost/restconf/operations/wrong:example)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"yang module not found"}}} ' expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' $RCPROTO://localhost/restconf/operations/wrong:example)" 0 "HTTP/1.1 412 Precondition Failed" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"yang module not found"}}}'
new "restconf local client rpc using POST xml" new "restconf local client rpc using POST xml"
ret=$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":"example"}}' $RCPROTO://localhost/restconf/operations/clixon-example:client-rpc) ret=$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":"example"}}' $RCPROTO://localhost/restconf/operations/clixon-example:client-rpc)
expect='<output xmlns="urn:example:clixon"><x>example</x></output>' expect='<output xmlns="urn:example:clixon"><x>example</x></output>'
match=`echo $ret | grep --null -Eo "$expect"` match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
@ -277,7 +268,7 @@ new "restconf Add subtree without key (expected error)"
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =interface, expected' expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =interface, expected'
new "restconf Add subtree with too many keys (expected error)" new "restconf Add subtree with too many keys (expected error)"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}} ' expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 "HTTP/1.1 400 Bad Request" '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}}'
if [ $RC -ne 0 ]; then if [ $RC -ne 0 ]; then
new "Kill restconf daemon" new "Kill restconf daemon"

View file

@ -94,22 +94,22 @@ if [ $RC -ne 0 ]; then
fi fi
new "restconf POST tree without key" new "restconf POST tree without key"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"name"},"error-severity":"error","error-message":"Mandatory key"}}} ' expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 400 Bad Request" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"name"},"error-severity":"error","error-message":"Mandatory key"}}}'
new "restconf POST initial tree" new "restconf POST initial tree"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created"
new "restconf POST top without namespace" new "restconf POST top without namespace"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"cont1":{"interface":{"name":"local0","type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object cont1 is not qualified with namespace which is a MUST according to RFC 7951"}}} ' expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"cont1":{"interface":{"name":"local0","type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 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 cont1 is not qualified with namespace which is a MUST according to RFC 7951"}}} '
new "restconf GET datastore initial" new "restconf GET datastore initial"
expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}' expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 200 OK" '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}'
new "restconf GET interface subtree" new "restconf GET interface subtree"
expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0" 0 '{"example:interface":\[{"name":"local0","type":"regular"}\]}' expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0)" 0 "HTTP/1.1 200 OK" '{"example:interface":\[{"name":"local0","type":"regular"}\]}'
new "restconf GET interface subtree xml" new "restconf GET interface subtree xml"
ret=$(curl -s -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0) ret=$(curl -sik -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0)
expect='<interface xmlns="urn:example:clixon"><name>local0</name><type>regular</type></interface>' expect='<interface xmlns="urn:example:clixon"><name>local0</name><type>regular</type></interface>'
match=`echo $ret | grep --null -Eo "$expect"` match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
@ -117,83 +117,83 @@ if [ -z "$match" ]; then
fi fi
new "restconf GET if-type" new "restconf GET if-type"
expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0/type" 0 '{"example:type":"regular"}' expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0/type)" 0 "HTTP/1.1 200 OK" '{"example:type":"regular"}'
new "restconf POST interface without mandatory type" new "restconf POST interface without mandatory type"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:cont1 -d '{"example:interface":{"name":"TEST"}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"type"},"error-severity":"error","error-message":"Mandatory variable"}}} ' expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:cont1 -d '{"example:interface":{"name":"TEST"}}')" 0 "HTTP/1.1 400 Bad Request" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"type"},"error-severity":"error","error-message":"Mandatory variable"}}}'
new "restconf POST interface without mandatory key" new "restconf POST interface without mandatory key"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:cont1 -d '{"example:interface":{"type":"regular"}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"name"},"error-severity":"error","error-message":"Mandatory key"}}} ' expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:cont1 -d '{"example:interface":{"type":"regular"}}')" 0 "HTTP/1.1 400 Bad Request" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"name"},"error-severity":"error","error-message":"Mandatory key"}}}'
new "restconf POST interface" new "restconf POST interface"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 201 Created" expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 201 Created"
new "restconf POST interface without namespace" new "restconf POST interface without namespace"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"interface":{"name":"TEST2","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object interface is not qualified with namespace which is a MUST according to RFC 7951"}}} ' expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"interface":{"name":"TEST2","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1)" 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 interface is not qualified with namespace which is a MUST according to RFC 7951"}}}'
new "restconf POST again" new "restconf POST again"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}} ' expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 409 Conflict" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}'
new "restconf POST from top" new "restconf POST from top"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"TEST","type":"eth0"}}}' $RCPROTO://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}} ' expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"TEST","type":"eth0"}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 409 Conflict" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}'
new "restconf DELETE" new "restconf DELETE"
expectfn "curl -si -X DELETE $RCPROTO://localhost/restconf/data/example:cont1" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -sik -X DELETE $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 204 No Content"
new "restconf GET null datastore" new "restconf GET null datastore"
expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 404 Not Found" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}'
new "restconf POST initial tree" new "restconf POST initial tree"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 "" expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created"
new "restconf GET initial tree" new "restconf GET initial tree"
expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}' expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 200 OK" '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}'
new "restconf DELETE whole datastore" new "restconf DELETE whole datastore"
expectfn "curl -s -X DELETE $RCPROTO://localhost/restconf/data" 0 "" expectpart "$(curl -sik -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content"
new "restconf GET null datastore" new "restconf GET null datastore"
expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 404 Not Found" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}'
new "restconf PUT initial datastore" new "restconf PUT initial datastore"
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created"
new "restconf GET datastore" new "restconf GET datastore"
expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}' expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 200 OK" '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}'
new "restconf PUT replace datastore" new "restconf PUT replace datastore"
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont2":{"name":"foo"}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont2":{"name":"foo"}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content"
new "restconf GET replaced datastore" new "restconf GET replaced datastore"
expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont2" 0 '{"example:cont2":{"name":"foo"}}' expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:cont2)" 0 "HTTP/1.1 200 OK" '{"example:cont2":{"name":"foo"}}'
new "restconf PUT initial datastore again" new "restconf PUT initial datastore again"
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content"
new "restconf PUT change interface" new "restconf PUT change interface"
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"local0","type":"atm0"}}' $RCPROTO://localhost/restconf/data/example:cont1/interface=local0)" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"local0","type":"atm0"}}' $RCPROTO://localhost/restconf/data/example:cont1/interface=local0)" 0 "HTTP/1.1 204 No Content"
new "restconf GET datastore atm" new "restconf GET datastore atm"
expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"atm0"}\]}}' expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 200 OK" '{"example:cont1":{"interface":\[{"name":"local0","type":"atm0"}\]}}'
new "restconf PUT add interface" new "restconf PUT add interface"
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1/interface=TEST)" 0 "HTTP/1.1 201 Created" expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1/interface=TEST)" 0 "HTTP/1.1 201 Created"
new "restconf PUT change key error" new "restconf PUT change key error"
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"ALPHA","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1/interface=TEST)" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} ' expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"ALPHA","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1/interface=TEST)" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}'
new "restconf PUT change type to eth0 (non-key sub-element to list)" new "restconf PUT change type to eth0 (non-key sub-element to list)"
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:type":"eth0"}' $RCPROTO://localhost/restconf/data/example:cont1/interface=local0/type)" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:type":"eth0"}' $RCPROTO://localhost/restconf/data/example:cont1/interface=local0/type)" 0 "HTTP/1.1 204 No Content"
new "restconf GET datastore eth" new "restconf GET datastore eth"
expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0" 0 '{"example:interface":\[{"name":"local0","type":"eth0"}\]}' expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0)" 0 "HTTP/1.1 200 OK" '{"example:interface":\[{"name":"local0","type":"eth0"}\]}'
#--------------- json type tests #--------------- json type tests
new "restconf POST type x3" new "restconf POST type x3 POST"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}}' $RCPROTO://localhost/restconf/data)" 0 '' expectpart "$(curl -sik -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: http://localhost/restconf/data/example:types"
new "restconf POST type x3" new "restconf POST type x3 GET"
expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:types" 0 '{"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}}' expectpart "$(curl -sik -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"}}'
if [ $RC -ne 0 ]; then if [ $RC -ne 0 ]; then
new "Kill restconf daemon" new "Kill restconf daemon"

View file

@ -92,78 +92,76 @@ if [ $RC -ne 0 ]; then
fi fi
new "restconf PUT add whole list entry" new "restconf PUT add whole list entry"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"x","c":"y","nonkey":"0"}}')" 0 '' expectpart "$(curl -sik -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"
# GETs to ensure you get list [] in JSON # GETs to ensure you get list [] in JSON
new "restconf GET whole list entry" new "restconf GET whole list entry"
expecteq "$(curl -s -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/)" 0 '{"list:c":{"a":[{"b":"x","c":"y","nonkey":"0"}]}} expectpart "$(curl -sik -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"}\]}}'
'
new "restconf GET list entry itself (should fail)" new "restconf GET list entry itself (should fail)"
expectpart "$(curl -si -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a)" 0 'HTTP/1.1 400 Bad Request' '^{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =a, expected ' expectpart "$(curl -si -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a)" 0 'HTTP/1.1 400 Bad Request' '^{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =a, expected '
new "restconf GET list entry" new "restconf GET list entry"
expecteq "$(curl -s -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y)" 0 '{"list:a":[{"b":"x","c":"y","nonkey":"0"}]} expectpart "$(curl -sik -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"}\]}'
'
new "restconf PUT add whole list entry XML" new "restconf PUT add whole list entry XML"
expecteq "$(curl -s -X PUT -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '<a xmlns="urn:example:clixon"><b>xx</b><c>xy</c><nonkey>0</nonkey></a>' $RCPROTO://localhost/restconf/data/list:c/a=xx,xy)" 0 '' expectpart "$(curl -sik -X PUT -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '<a xmlns="urn:example:clixon"><b>xx</b><c>xy</c><nonkey>0</nonkey></a>' $RCPROTO://localhost/restconf/data/list:c/a=xx,xy)" 0 "HTTP/1.1 201 Created"
expectpart "$(curl -si -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a/b)" 0 'HTTP/1.1 400 Bad Request' '^{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =a, expected ' expectpart "$(curl -sik -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a/b)" 0 'HTTP/1.1 400 Bad Request' '^{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =a, expected '
new "restconf PUT change whole list entry (same keys)" new "restconf PUT change whole list entry (same keys)"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"x","c":"y","nonkey":"z"}}')" 0 '' expectpart "$(curl -sik -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"
new "restconf PUT change whole list entry (no namespace)(expect fail)" new "restconf PUT change whole list entry (no namespace)(expect fail)"
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"a":{"b":"x","c":"y","nonkey":"z"}}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object a is not qualified with namespace which is a MUST according to RFC 7951"}}} ' expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"a":{"b":"x","c":"y","nonkey":"z"}}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object a is not qualified with namespace which is a MUST according to RFC 7951"}}} '
new "restconf PUT change list entry (wrong keys)(expect fail)" new "restconf PUT change list entry (wrong keys)(expect fail)"
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"y","c":"x"}}')" 0 '412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} ' expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"y","c":"x"}}')" 0 '412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} '
new "restconf PUT change list entry (wrong keys)(expect fail) XML" new "restconf PUT change list entry (wrong keys)(expect fail) XML"
expecteq "$(curl -s -X PUT -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '<a xmlns="urn:example:clixon"><b>xy</b><c>xz</c><nonkey>0</nonkey></a>' $RCPROTO://localhost/restconf/data/list:c/a=xx,xy)" 0 '<errors xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>api-path keys do not match data keys</error-message></error></errors> ' expectpart "$(curl -sik -X PUT -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '<a xmlns="urn:example:clixon"><b>xy</b><c>xz</c><nonkey>0</nonkey></a>' $RCPROTO://localhost/restconf/data/list:c/a=xx,xy)" 0 "HTTP/1.1 412 Precondition Failed" '<errors xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>api-path keys do not match data keys</error-message></error></errors> '
new "restconf PUT change list entry (just one key)(expect fail)" new "restconf PUT change list entry (just one key)(expect fail)"
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x -d '{"list:a":{"b":"x"}}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}} ' expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x -d '{"list:a":{"b":"x"}}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}} '
new "restconf PUT sub non-key" new "restconf PUT sub non-key"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/nonkey -d '{"list:nonkey":"u"}')" 0 '' expectpart "$(curl -sik -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"
new "restconf PUT sub key same value" new "restconf PUT sub key same value"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/b -d '{"list:b":"x"}')" 0 '' expectpart "$(curl -sik -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"
new "restconf PUT just key other value (should fail)" new "restconf PUT just key other value (should fail)"
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x/b -d '{"b":"y"}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}}' expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x/b -d '{"b":"y"}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}}'
new "restconf PUT add leaf-list entry" new "restconf PUT add leaf-list entry"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/d=x -d '{"list:d":"x"}')" 0 '' expectpart "$(curl -sik -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"
new "restconf PUT change leaf-list entry (expect fail)" new "restconf PUT change leaf-list entry (expect fail)"
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/d=x -d '{"list:d":"y"}')" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}' expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/d=x -d '{"list:d":"y"}')" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}'
new "restconf PUT list-list" new "restconf PUT list-list"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z -d '{"list:e":{"f":"z","nonkey":"0"}}')" 0 '' expectpart "$(curl -sik -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"
new "restconf PUT change list-lst entry (wrong keys)(expect fail)" new "restconf PUT change list-lst entry (wrong keys)(expect fail)"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z -d '{"list:e":{"f":"wrong","nonkey":"0"}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} ' expectpart "$(curl -sik -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"}}}'
new "restconf PUT list-list sub non-key" new "restconf PUT list-list sub non-key"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z/nonkey -d '{"list:nonkey":"u"}')" 0 '' expectpart "$(curl -sik -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"
new "restconf PUT list-list single first key" new "restconf PUT list-list single first key"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x/e=z/f -d '{"f":"z"}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}} ' expectpart "$(curl -sik -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"}}}'
new "restconf PUT list-list just key ok" new "restconf PUT list-list just key ok"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z/f -d '{"list:f":"z"}')" 0 '' expectpart "$(curl -sik -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"
new "restconf PUT list-list just key just key wrong value (should fail)" new "restconf PUT list-list just key just key wrong value (should fail)"
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $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 -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z/f -d '{"list:f":"wrong"}')" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} '
new "restconf PUT add list+leaf-list entry" new "restconf PUT add list+leaf-list entry"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/f=u -d '{"list:f":"u"}')" 0 '' expectpart "$(curl -sik -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"
new "restconf PUT change list+leaf-list entry (expect fail)" new "restconf PUT change list+leaf-list entry (expect fail)"
expectpart "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/f=u -d '{"list:f":"w"}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}' expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/f=u -d '{"list:f":"w"}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}'
if [ $RC -ne 0 ]; then if [ $RC -ne 0 ]; then
new "Kill restconf daemon" new "Kill restconf daemon"

View file

@ -74,16 +74,16 @@ testrun(){
wait_restconf wait_restconf
new "restconf put 42" new "restconf put 42"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:x/y=42 -d '{"example:y":{"a":"42","b":"42"}}')" 0 "" expectpart "$(curl -sik -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"
new "restconf put 99" new "restconf put 99"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:x/y=99 -d '{"example:y":{"a":"99","b":"99"}}')" 0 "" expectpart "$(curl -sik -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"
new "restconf post 123" new "restconf post 123"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:x -d '{"example:y":{"a":"123","b":"123"}}')" 0 "" expectpart "$(curl -sik -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"
new "restconf delete 42" new "restconf delete 42"
expecteq "$(curl -s -X DELETE $RCPROTO://localhost/restconf/data/example:x/y=42)" 0 "" expectpart "$(curl -sik -X DELETE $RCPROTO://localhost/restconf/data/example:x/y=42)" 0 "HTTP/1.1 204 No Content"
new "Kill restconf daemon" new "Kill restconf daemon"
stop_restconf stop_restconf

View file

@ -205,16 +205,16 @@ expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<
# Now same with restconf # Now same with restconf
new "restconf edit main" new "restconf edit main"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data -d '{"main:main":{"x":"foo","ext":"foo"}}')" 0 'HTTP/1.1 201 Created' expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"main:main":{"x":"foo","ext":"foo"}}')" 0 'HTTP/1.1 201 Created'
new "restconf edit sub1" new "restconf edit sub1"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data -d '{"main:sub1":{"x":"foo","ext1":"foo"}}')" 0 'HTTP/1.1 201 Created' expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"main:sub1":{"x":"foo","ext1":"foo"}}')" 0 'HTTP/1.1 201 Created'
new "restconf edit sub2" new "restconf edit sub2"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data -d '{"main:sub2":{"x":"foo","ext2":"foo"}}')" 0 'HTTP/1.1 201 Created' expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"main:sub2":{"x":"foo","ext2":"foo"}}')" 0 'HTTP/1.1 201 Created'
new "restconf check main/sub1/sub2 contents" new "restconf check main/sub1/sub2 contents"
expectpart "$(curl -si -X GET http://localhost/restconf/data?content=config)" 0 'HTTP/1.1 200 OK' '{"data":{"main:main":{"ext":"foo","x":"foo"},"main:sub1":{"ext1":"foo","x":"foo"},"main:sub2":{"ext2":"foo","x":"foo"}}}' expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data?content=config)" 0 'HTTP/1.1 200 OK' '{"data":{"main:main":{"ext":"foo","x":"foo"},"main:sub1":{"ext1":"foo","x":"foo"},"main:sub2":{"ext2":"foo","x":"foo"}}}'
new "Kill restconf daemon" new "Kill restconf daemon"
stop_restconf stop_restconf

View file

@ -103,24 +103,23 @@ new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "restconf set x in example1" new "restconf set x in example1"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example1:x":42}' http://localhost/restconf/data)" 0 '' expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"example1:x":42}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created"
new "restconf get config example1" new "restconf get config example1"
expecteq "$(curl -s -X GET http://localhost/restconf/data/example1:x)" 0 '{"example1:x":42} expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example1:x)" 0 "HTTP/1.1 200 OK" '{"example1:x":42}
' '
new "restconf set x in example2" new "restconf set x in example2"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example2:x":{"y":99}}' http://localhost/restconf/data)" 0 '' expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"example2:x":{"y":99}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created"
# XXX GET ../example1:x is translated to select=/x which gets both example1&2 # XXX GET ../example1:x is translated to select=/x which gets both example1&2
#new "restconf get config example1" #new "restconf get config example1"
#expecteq "$(curl -s -X GET http://localhost/restconf/data/example1:x)" 0 '{"example1:x":42} #expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example1:x)" 0 "HTTP/1.1 200 OK" '{"example1:x":42}'
# '
# XXX GET ../example2:x is translated to select=/x which gets both example1&2 # XXX GET ../example2:x is translated to select=/x which gets both example1&2
#new "restconf get config example2" #new "restconf get config example2"
#expecteq "$(curl -s -X GET http://localhost/restconf/data/example2:x)" 0 '{"example2:x":{"y":42}} #expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example2:x)" 0 "HTTP/1.1 200 OK" '{"example2:x":{"y":42}}'
# '
new "restconf get config example1 and example2" new "restconf get config example1 and example2"
ret=$(curl -s -X GET http://localhost/restconf/data) ret=$(curl -s -X GET http://localhost/restconf/data)