* 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:
parent
c18c40434f
commit
6e714beea5
37 changed files with 1619 additions and 1128 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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_ */
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
495
apps/restconf/restconf_err.c
Normal file
495
apps/restconf/restconf_err.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -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_ */
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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], '=', ¶m, &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], '=', ¶m, 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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], '=', ¶m, &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], '=', ¶m, 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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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_ */
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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_ */
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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_ */
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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_ */
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
32
test/lib.sh
32
test/lib.sh
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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>"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 "^$"
|
||||||
|
|
|
||||||
|
|
@ -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>"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;$"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue