restconf post/put/head/patch, memtests

This commit is contained in:
Olof hagsand 2017-03-26 15:14:37 +02:00
parent eec5896797
commit 19813a4d9c
17 changed files with 782 additions and 365 deletions

View file

@ -66,7 +66,9 @@
/* clicon */
#include <clixon/clixon.h>
/* restconf */
#include "restconf_lib.h"
#include "restconf_methods.h"
/* Command line options to be passed to getopt(3) */
#define RESTCONF_OPTS "hDf:p:"
@ -75,301 +77,6 @@
resource ([RFC6415]) */
#define RESTCONF_API_ROOT "/restconf/"
/*! REST OPTIONS method
* According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] head Set if HEAD request instead of GET
* @code
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
* @endcode
*/
static int
api_data_options(clicon_handle h,
FCGX_Request *r,
cvec *pcvec,
int pi,
cvec *qvec,
int head)
{
int retval = -1;
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
FCGX_FPrintF(r->out, "GET, HEAD, OPTIONS, PUT, POST, DELETE\r\n");
retval = 0;
return retval;
}
/*! Generic REST GET method
* According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING)
* @code
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
* @endcode
* XXX: cant find a way to use Accept request field to choose Content-Type
* I would like to support both xml and json.
* Request may contain
* Accept: application/yang.data+json,application/yang.data+xml
* Response contains one of:
* Content-Type: application/yang.data+xml
* Content-Type: application/yang.data+json
* NOTE: If a retrieval request for a data resource representing a YANG leaf-
* list or list object identifies more than one instance, and XML
* encoding is used in the response, then an error response containing a
* "400 Bad Request" status-line MUST be returned by the server.
*/
static int
api_data_get(clicon_handle h,
FCGX_Request *r,
cvec *pcvec,
int pi,
cvec *qvec)
{
int retval = -1;
cg_var *cv;
char *val;
char *v;
int i;
cbuf *path = NULL;
cbuf *path1 = NULL;
cbuf *cbx = NULL;
cxobj **vec = NULL;
yang_spec *yspec;
yang_stmt *y;
yang_stmt *ykey;
char *name;
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
cxobj *xret = NULL;
clicon_debug(1, "%s", __FUNCTION__);
yspec = clicon_dbspec_yang(h);
if ((path = cbuf_new()) == NULL)
goto done;
if ((path1 = cbuf_new()) == NULL) /* without [] qualifiers */
goto done;
cv = NULL;
cprintf(path1, "/");
/* translate eg a/b=c -> a/[b=c] */
for (i=pi; i<cvec_len(pcvec); i++){
cv = cvec_i(pcvec, i);
name = cv_name_get(cv);
clicon_debug(1, "[%d] cvname:%s", i, name);
clicon_debug(1, "cv2str%d", cv2str(cv, NULL, 0));
if (i == pi){
if ((y = yang_find_topnode(yspec, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
goto done;
}
}
else{
if ((y = yang_find_syntax((yang_node*)y, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
goto done;
}
}
/* Check if has value, means '=' */
if (cv2str(cv, NULL, 0) > 0){
if ((val = cv2str_dup(cv)) == NULL)
goto done;
v = val;
/* XXX sync with yang */
while((v=index(v, ',')) != NULL){
*v = '\0';
v++;
}
/* Find keys */
if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
__FUNCTION__, y->ys_argument);
notfound(r);
goto done;
}
clicon_debug(1, "ykey:%s", ykey->ys_argument);
/* The value is a list of keys: <key>[ <key>]* */
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
goto done;
cvi = NULL;
/* Iterate over individual yang keys */
cprintf(path, "/%s", name);
v = val;
while ((cvi = cvec_each(cvk, cvi)) != NULL){
cprintf(path, "[%s=%s]", cv_string_get(cvi), v);
v += strlen(v)+1;
}
if (val)
free(val);
}
else{
cprintf(path, "%s%s", (i==pi?"":"/"), name);
cprintf(path1, "/%s", name);
}
}
clicon_debug(1, "%s path:%s", __FUNCTION__, cbuf_get(path));
if (clicon_rpc_get_config(h, "running", cbuf_get(path), &xret) < 0){
notfound(r);
goto done;
}
if ((cbx = cbuf_new()) == NULL)
goto done;
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: application/yang.data+json\r\n");
FCGX_FPrintF(r->out, "\r\n");
clicon_debug(1, "%s name:%s child:%d", __FUNCTION__, xml_name(xret), xml_child_nr(xret));
vec = xml_childvec_get(xret);
if (xml2json_cbuf_vec(cbx, vec, xml_child_nr(xret), 0) < 0)
goto done;
clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx));
FCGX_FPrintF(r->out, "%s", cbuf_get(cbx));
FCGX_FPrintF(r->out, "\r\n\r\n");
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cbx)
cbuf_free(cbx);
if (path)
cbuf_free(path);
if (path1)
cbuf_free(path1);
if (xret)
xml_free(xret);
return retval;
}
/*! Generic REST DELETE method
* @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] pi Offset, where path starts
* Example:
* curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0
*/
static int
api_data_delete(clicon_handle h,
FCGX_Request *r,
char *api_path,
int pi)
{
int retval = -1;
int i;
clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path);
for (i=0; i<pi; i++)
api_path = index(api_path+1, '/');
if (clicon_rpc_edit_config(h, "candidate",
OP_REMOVE,
api_path,
"<config/>") < 0){
notfound(r);
goto done;
}
if (clicon_rpc_commit(h) < 0)
goto done;
FCGX_SetExitStatus(201, r->out);
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval;
}
/*! Generic REST PUT method
* @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] dvec Stream input data
* @param[in] post POST instead of PUT
* Example:
curl -X PUT -d {\"enabled\":\"false\"} http://127.0.0.1/restconf/data/interfaces/interface=eth1
*
PUT:
if the PUT request creates a new resource,
a "201 Created" status-line is returned. If an existing resource is
modified, a "204 No Content" status-line is returned.
POST:
If the POST method succeeds, a "201 Created" status-line is returned
and there is no response message-body. A "Location" header
identifying the child resource that was created MUST be present in
the response in this case.
If the data resource already exists, then the POST request MUST fail
and a "409 Conflict" status-line MUST be returned.
*/
static int
api_data_put(clicon_handle h,
FCGX_Request *r,
char *api_path,
cvec *pcvec,
int pi,
cvec *qvec,
char *data,
int post)
{
int retval = -1;
int i;
cxobj *xdata = NULL;
cbuf *cbx = NULL;
cxobj *x;
clicon_debug(1, "%s api_path:%s json:%s",
__FUNCTION__,
api_path, data);
for (i=0; i<pi; i++)
api_path = index(api_path+1, '/');
/* Parse input data as json into xml */
if (json_parse_str(data, &xdata) < 0){
clicon_debug(1, "%s json fail", __FUNCTION__);
goto done;
}
if ((cbx = cbuf_new()) == NULL)
goto done;
cprintf(cbx, "<config>");
x = NULL;
while ((x = xml_child_each(xdata, x, -1)) != NULL) {
if (clicon_xml2cbuf(cbx, x, 0, 0) < 0)
goto done;
}
cprintf(cbx, "</config>");
clicon_debug(1, "%s cbx: %s api_path:%s",__FUNCTION__, cbuf_get(cbx), api_path);
if (clicon_rpc_edit_config(h, "candidate",
OP_MERGE,
api_path,
cbuf_get(cbx)) < 0){
notfound(r);
goto done;
}
if (clicon_rpc_commit(h) < 0)
goto done;
FCGX_SetExitStatus(201, r->out); /* Created */
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (xdata)
xml_free(xdata);
if (cbx)
cbuf_free(cbx);
return retval;
}
/*! Generic REST method, GET, PUT, DELETE
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
@ -393,14 +100,17 @@ api_data(clicon_handle h,
clicon_debug(1, "%s", __FUNCTION__);
request_method = FCGX_GetParam("REQUEST_METHOD", r->envp);
clicon_debug(1, "%s method:%s", __FUNCTION__, request_method);
if (strcmp(request_method, "OPTIONS")==0)
retval = api_data_options(h, r, pcvec, pi, qvec, 0);
retval = api_data_options(h, r);
else if (strcmp(request_method, "HEAD")==0)
retval = api_data_head(h, r, pcvec, pi, qvec);
else if (strcmp(request_method, "GET")==0)
retval = api_data_get(h, r, pcvec, pi, qvec);
else if (strcmp(request_method, "PUT")==0)
retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data, 0);
else if (strcmp(request_method, "POST")==0)
retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data, 1);
retval = api_data_post(h, r, api_path, pcvec, pi, qvec, data);
else if (strcmp(request_method, "PUT")==0)
retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data);
else if (strcmp(request_method, "DELETE")==0)
retval = api_data_delete(h, r, api_path, pi);
else
@ -470,7 +180,7 @@ request_process(clicon_handle h,
else
retval = notfound(r);
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
clicon_debug(1, "%s retval:%d K", __FUNCTION__, retval);
if (dvec)
cvec_free(dvec);
if (qvec)
@ -488,6 +198,7 @@ restconf_terminate(clicon_handle h)
{
yang_spec *yspec;
clicon_debug(0, "%s", __FUNCTION__);
clicon_rpc_close_session(h);
if ((yspec = clicon_dbspec_yang(h)) != NULL)
yspec_free(yspec);
@ -495,6 +206,27 @@ restconf_terminate(clicon_handle h)
return 0;
}
/* Need global variable to for signal handler */
static clicon_handle _CLICON_HANDLE = NULL;
/*! Signall terminates process
*/
static void
restconf_sig_term(int arg)
{
static int i=0;
if (i++ == 0)
clicon_log(LOG_NOTICE, "%s: %s: pid: %u Signal %d",
__PROGRAM__, __FUNCTION__, getpid(), arg);
else
exit(-1);
if (_CLICON_HANDLE)
restconf_terminate(_CLICON_HANDLE);
clicon_exit_set(); /* checked in event_loop() */
exit(-1);
}
/*! Usage help routine
* @param[in] argv0 command line
* @param[in] h Clicon handle
@ -538,7 +270,7 @@ main(int argc,
/* Create handle */
if ((h = clicon_handle_init()) == NULL)
goto done;
_CLICON_HANDLE = h; /* for termination handling */
while ((c = getopt(argc, argv, RESTCONF_OPTS)) != -1)
switch (c) {
case 'h':
@ -566,6 +298,15 @@ main(int argc,
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_SYSLOG);
clicon_debug_init(debug, NULL);
clicon_log(LOG_NOTICE, "%s: %u Started", __PROGRAM__, getpid());
if (set_signal(SIGTERM, restconf_sig_term, NULL) < 0){
clicon_err(OE_DEMON, errno, "Setting signal");
goto done;
}
if (set_signal(SIGINT, restconf_sig_term, NULL) < 0){
clicon_err(OE_DEMON, errno, "Setting signal");
goto done;
}
/* Find and read configfile */
if (clicon_options_main(h) < 0)