From 19813a4d9c746c0f93465d8d50f7304eaf8e8138 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 26 Mar 2017 15:14:37 +0200 Subject: [PATCH] restconf post/put/head/patch, memtests --- apps/backend/backend_client.c | 2 - apps/backend/clixon_backend_handle.c | 2 +- apps/cli/cli_show.c | 2 - apps/netconf/netconf_main.c | 8 +- apps/restconf/Makefile.in | 1 + apps/restconf/restconf_lib.c | 33 ++ apps/restconf/restconf_lib.h | 35 +- apps/restconf/restconf_main.c | 343 +++--------------- apps/restconf/restconf_methods.c | 513 +++++++++++++++++++++++++++ apps/restconf/restconf_methods.h | 63 ++++ example/routing_backend.c | 12 +- lib/src/clixon_proto_client.c | 1 + lib/src/clixon_xml_db.c | 1 - lib/src/clixon_xsl.c | 26 +- test/test1.sh | 48 +-- test/test2.sh | 44 +-- test/test3.sh | 13 +- 17 files changed, 782 insertions(+), 365 deletions(-) create mode 100644 apps/restconf/restconf_methods.c create mode 100644 apps/restconf/restconf_methods.h diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index a4a3e651..41d36a86 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -85,7 +85,6 @@ client_subscription_add(struct client_entry *ce, { struct client_subscription *su = NULL; - fprintf(stderr, "%s stream:%s filter:%s\n", __FUNCTION__, stream, filter); if ((su = malloc(sizeof(*su))) == NULL){ clicon_err(OE_PLUGIN, errno, "malloc"); goto done; @@ -118,7 +117,6 @@ client_subscription_delete(struct client_entry *ce, struct client_subscription *su; struct client_subscription **su_prev; - fprintf(stderr, "%s stream:%s\n", __FUNCTION__, su0->su_stream); su_prev = &ce->ce_subscription; /* this points to stack and is not real backpointer */ for (su = *su_prev; su; su = su->su_next){ if (su == su0){ diff --git a/apps/backend/clixon_backend_handle.c b/apps/backend/clixon_backend_handle.c index e5308886..a795d7b0 100644 --- a/apps/backend/clixon_backend_handle.c +++ b/apps/backend/clixon_backend_handle.c @@ -441,7 +441,7 @@ backend_netconf_register_callback(clicon_handle h, memset (nr, 0, sizeof (*nr)); nr->nr_callback = cb; nr->nr_arg = arg; - nr->nr_tag = strdup(tag); /* strdup */ + nr->nr_tag = strdup(tag); /* XXX strdup memleak */ INSQ(nr, deps); return 0; catch: diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 3f80310a..561ed85e 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -614,8 +614,6 @@ show_confv_as_command(clicon_handle h, enum genmodel_type gt; int retval = -1; - if ((xt = xml_new("tmp", NULL)) == NULL) - goto done; if (show_confv_as(h, cvv, argv, &xt) < 0) goto done; xc = NULL; /* Dont print xt itself */ diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index cfdda1b4..e585a273 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -178,7 +178,8 @@ netconf_input_cb(int s, unsigned char buf[BUFSIZ]; int i; int len; - static cbuf *cb; /* XXX: should use ce state? */ + // static cbuf *cb; /* XXX: should use ce state? */ + cbuf *cb=NULL; /* XXX: should use ce state? */ int xml_state = 0; int retval = -1; @@ -221,7 +222,8 @@ netconf_input_cb(int s, } retval = 0; done: - // cbuf_free(cb); + if (cb) + cbuf_free(cb); if (cc_closed) retval = -1; return retval; @@ -257,9 +259,11 @@ netconf_terminate(clicon_handle h) { yang_spec *yspec; + clicon_rpc_close_session(h); if ((yspec = clicon_dbspec_yang(h)) != NULL) yspec_free(yspec); + event_exit(); clicon_handle_exit(h); return 0; } diff --git a/apps/restconf/Makefile.in b/apps/restconf/Makefile.in index 54c80bcf..ddc845d0 100644 --- a/apps/restconf/Makefile.in +++ b/apps/restconf/Makefile.in @@ -62,6 +62,7 @@ CPPFLAGS = @CPPFLAGS@ INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@ SRC = restconf_lib.c +SRC += restconf_methods.c OBJS = $(SRC:.c=.o) diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 1aaa802b..adda4275 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -1,6 +1,39 @@ /* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren + + 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 ***** + */ + #include #include #include diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index 548e30e1..0a1bdd69 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -1,9 +1,36 @@ /* * - * $COPYRIGHTSTATEMENT$ - * - * $LICENSE$ - * This is backend headend sender code, ie communication with a pmagent + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren + + 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 ***** + */ #ifndef _RESTCONF_LIB_H_ diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 514b0daf..043d6c63 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -66,7 +66,9 @@ /* clicon */ #include +/* 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; iys_argument); - notfound(r); - goto done; - } - clicon_debug(1, "ykey:%s", ykey->ys_argument); - - /* The value is a list of keys: [ ]* */ - 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") < 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"); - x = NULL; - while ((x = xml_child_each(xdata, x, -1)) != NULL) { - if (clicon_xml2cbuf(cbx, x, 0, 0) < 0) - goto done; - } - cprintf(cbx, ""); - 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) diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c new file mode 100644 index 00000000..90f4c6ff --- /dev/null +++ b/apps/restconf/restconf_methods.c @@ -0,0 +1,513 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren + + 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 draft-ietf-netconf-restconf-13.txt [draft] + * See draft-ietf-netconf-restconf-17.txt [draft] + + * sudo apt-get install libfcgi-dev + * gcc -o fastcgi fastcgi.c -lfcgi + + * sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/routing.conf " -s /bin/sh www-data + + * This is the interface: + * api/data/profile=/metric= PUT data:enable= + * api/test + +----------------------------+--------------------------------------+ + | 100 Continue | POST accepted, 201 should follow | + | 200 OK | Success with response message-body | + | 201 Created | POST to create a resource success | + | 204 No Content | Success without response message- | + | | body | + | 304 Not Modified | Conditional operation not done | + | 400 Bad Request | Invalid request message | + | 401 Unauthorized | Client cannot be authenticated | + | 403 Forbidden | Access to resource denied | + | 404 Not Found | Resource target or resource node not | + | | found | + | 405 Method Not Allowed | Method not allowed for target | + | | resource | + | 409 Conflict | Resource or lock in use | + | 412 Precondition Failed | Conditional method is false | + | 413 Request Entity Too | too-big error | + | Large | | + | 414 Request-URI Too Large | too-big error | + | 415 Unsupported Media Type | non RESTCONF media type | + | 500 Internal Server Error | operation-failed | + | 501 Not Implemented | unknown-operation | + | 503 Service Unavailable | Recoverable server error | + +----------------------------+--------------------------------------+ +Mapping netconf error-tag -> status code + +-------------------------+-------------+ + | | status code | + +-------------------------+-------------+ + | in-use | 409 | + | invalid-value | 400 | + | too-big | 413 | + | missing-attribute | 400 | + | bad-attribute | 400 | + | unknown-attribute | 400 | + | bad-element | 400 | + | unknown-element | 400 | + | unknown-namespace | 400 | + | access-denied | 403 | + | lock-denied | 409 | + | resource-denied | 409 | + | rollback-failed | 500 | + | data-exists | 409 | + | data-missing | 409 | + | operation-not-supported | 501 | + | operation-failed | 500 | + | partial-operation | 500 | + | malformed-message | 400 | + +-------------------------+-------------+ + + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include + +#include "restconf_lib.h" +#include "restconf_methods.h" + +/*! REST OPTIONS method + * According to restconf (Sec 3.5.1.1 in [draft]) + * @param[in] h Clixon handle + * @param[in] r Fastcgi request handle + * @code + * curl -G http://localhost/restconf/data/interfaces/interface=eth0 + * @endcode + * Minimal support: + * 200 OK + * Allow: HEAD,GET,PUT,DELETE,OPTIONS + */ +int +api_data_options(clicon_handle h, + FCGX_Request *r) +{ + clicon_debug(1, "%s", __FUNCTION__); + FCGX_SetExitStatus(200, r->out); /* OK */ + FCGX_FPrintF(r->out, "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE\r\n"); + FCGX_FPrintF(r->out, "\r\n"); + return 0; +} + +/*! Generic GET (both HEAD and GET) + */ +static int +api_data_get_gen(clicon_handle h, + FCGX_Request *r, + cvec *pcvec, + int pi, + cvec *qvec, + int head) +{ + 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; iys_argument); + notfound(r); + goto done; + } + clicon_debug(1, "ykey:%s", ykey->ys_argument); + + /* The value is a list of keys: [ ]* */ + 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; + } + { + cbuf *cb = cbuf_new(); + clicon_xml2cbuf(cb, xret, 0, 0); + clicon_debug(1, "%s xret:%s", __FUNCTION__, cbuf_get(cb)); + cbuf_free(cb); + } + 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"); + if (head) + goto ok; + clicon_debug(1, "%s name:%s child:%d", __FUNCTION__, xml_name(xret), xml_child_nr(xret)); + vec = xml_childvec_get(xret); + clicon_debug(1, "%s xretnr:%d", __FUNCTION__, xml_child_nr(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", cbx?cbuf_get(cbx):""); + FCGX_FPrintF(r->out, "\r\n\r\n"); + ok: + 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; +} + +/*! REST HEAD method + * @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) + The HEAD method is sent by the client to retrieve just the header fields + that would be returned for the comparable GET method, without the + response message-body. + * Relation to netconf: none + */ +int +api_data_head(clicon_handle h, + FCGX_Request *r, + cvec *pcvec, + int pi, + cvec *qvec) +{ + return api_data_get_gen(h, r, pcvec, pi, qvec, 1); +} + +/*! 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. + * Netconf: , + */ +int +api_data_get(clicon_handle h, + FCGX_Request *r, + cvec *pcvec, + int pi, + cvec *qvec) +{ + return api_data_get_gen(h, r, pcvec, pi, qvec, 0); +} + +/*! Generic edit-config method: PUT/POST/PATCH + */ +static int +api_data_edit(clicon_handle h, + FCGX_Request *r, + char *api_path, + cvec *pcvec, + int pi, + cvec *qvec, + char *data, + enum operation_type operation) +{ + 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"); + x = NULL; + while ((x = xml_child_each(xdata, x, -1)) != NULL) { + if (clicon_xml2cbuf(cbx, x, 0, 0) < 0) + goto done; + } + cprintf(cbx, ""); + clicon_debug(1, "%s cbx: %s api_path:%s",__FUNCTION__, cbuf_get(cbx), api_path); + if (clicon_rpc_edit_config(h, "candidate", + operation, + 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; +} + + +/*! REST POST 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] data Stream input data + 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. + * Netconf: (nc:operation="create") | invoke an RPC operation + */ +int +api_data_post(clicon_handle h, + FCGX_Request *r, + char *api_path, + cvec *pcvec, + int pi, + cvec *qvec, + char *data) +{ + return api_data_edit(h, r, api_path, pcvec, pi, qvec, data, OP_CREATE); +} + +/*! 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] data Stream input data + * 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. + + * Netconf: (nc:operation="create/replace") + */ +int +api_data_put(clicon_handle h, + FCGX_Request *r, + char *api_path, + cvec *pcvec, + int pi, + cvec *qvec, + char *data) +{ + /* XXX: OP_CREATE? */ + return api_data_edit(h, r, api_path, pcvec, pi, qvec, data, OP_REPLACE); +} + +/*! Generic REST PATCH 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] data Stream input data + * Netconf: (nc:operation="merge") + */ +int +api_data_patch(clicon_handle h, + FCGX_Request *r, + char *api_path, + cvec *pcvec, + int pi, + cvec *qvec, + char *data) +{ + return api_data_edit(h, r, api_path, pcvec, pi, qvec, data, OP_MERGE); +} + +/*! 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 + * Netconf: (nc:operation="delete") + */ +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") < 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; +} + diff --git a/apps/restconf/restconf_methods.h b/apps/restconf/restconf_methods.h new file mode 100644 index 00000000..f6946f8a --- /dev/null +++ b/apps/restconf/restconf_methods.h @@ -0,0 +1,63 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren + + 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 ***** + + */ + + +#ifndef _RESTCONF_METHODS_H_ +#define _RESTCONF_METHODS_H_ + +/* + * Constants + */ + +/* + * Prototypes + */ +int api_data_options(clicon_handle h, FCGX_Request *r); +int api_data_head(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi, + cvec *qvec); +int api_data_get(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi, + cvec *qvec); +int api_data_post(clicon_handle h, FCGX_Request *r, char *api_path, + cvec *pcvec, int pi, + cvec *qvec, char *data); +int api_data_put(clicon_handle h, FCGX_Request *r, char *api_path, + cvec *pcvec, int pi, + cvec *qvec, char *data); +int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path, + cvec *pcvec, int pi, + cvec *qvec, char *data); +int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi); + +#endif /* _RESTCONF_METHODS_H_ */ diff --git a/example/routing_backend.c b/example/routing_backend.c index 59bac481..34e6fc80 100644 --- a/example/routing_backend.c +++ b/example/routing_backend.c @@ -62,7 +62,7 @@ int transaction_validate(clicon_handle h, transaction_data td) { - transaction_print(stderr, td); + // transaction_print(stderr, td); return 0; } @@ -73,16 +73,18 @@ transaction_commit(clicon_handle h, transaction_data td) { cxobj *target = transaction_target(td); /* wanted XML tree */ - cxobj **vec; + cxobj **vec = NULL; int i; size_t len; /* Get all added i/fs */ if (xpath_vec_flag(target, "//interface", XML_FLAG_ADD, &vec, &len) < 0) return -1; - for (i=0; imydesc" +expectfn "$clixon_cli -1f $clixon_cf -l o show xpath /interfaces/interface/description" "mydesc" new "cli delete description" -expectfn "clixon_cli -1f $clixon_cf -l o delete interfaces interface eth0 description mydesc" +expectfn "$clixon_cli -1f $clixon_cf -l o delete interfaces interface eth0 description mydesc" new "cli show xpath no description" -expectfn "clixon_cli -1f $clixon_cf -l o show xpath /interfaces/interface/description" "" +expectfn "$clixon_cli -1f $clixon_cf -l o show xpath /interfaces/interface/description" "" new "cli success validate" -expectfn "clixon_cli -1f $clixon_cf -l o validate" "" +expectfn "$clixon_cli -1f $clixon_cf -l o validate" "" new "cli commit" -expectfn "clixon_cli -1f $clixon_cf -l o commit" "" +expectfn "$clixon_cli -1f $clixon_cf -l o commit" "" new "cli save" -expectfn "clixon_cli -1f $clixon_cf -l o save /tmp/foo" "" +expectfn "$clixon_cli -1f $clixon_cf -l o save /tmp/foo" "" new "cli delete all" -expectfn "clixon_cli -1f $clixon_cf -l o delete all" "" +expectfn "$clixon_cli -1f $clixon_cf -l o delete all" "" new "cli load" -expectfn "clixon_cli -1f $clixon_cf -l o load /tmp/foo" "" +expectfn "$clixon_cli -1f $clixon_cf -l o load /tmp/foo" "" new "cli check load" -expectfn "clixon_cli -1f $clixon_cf -l o show conf cli" "^interfaces interface name eth0 +expectfn "$clixon_cli -1f $clixon_cf -l o show conf cli" "^interfaces interface name eth0 interfaces interface enabled true$" new "cli debug" -expectfn "clixon_cli -1f $clixon_cf -l o debug level 1" "" +expectfn "$clixon_cli -1f $clixon_cf -l o debug level 1" "" +# How to test this? +expectfn "$clixon_cli -1f $clixon_cf -l o debug level 0" "" new "cli downcall" -expectfn "clixon_cli -1f $clixon_cf -l o downcall \"This is a test =====\"" "^\"This is a test =====\"$" +expectfn "$clixon_cli -1f $clixon_cf -l o downcall \"This is a test =====\"" "^\"This is a test =====\"$" new "Kill backend" # Check if still alive diff --git a/test/test2.sh b/test/test2.sh index 93444e98..f23b070d 100755 --- a/test/test2.sh +++ b/test/test2.sh @@ -4,6 +4,10 @@ # include err() and new() functions . ./lib.sh +# For memcheck +# clixon_netconf="valgrind --leak-check=full --show-leak-kinds=all clixon_netconf" +clixon_netconf=clixon_netconf + # kill old backend (if any) new "kill old backend" sudo clixon_backend -zf $clixon_cf @@ -17,61 +21,61 @@ if [ $? -ne 0 ]; then err fi new "netconf get empty config" -expecteof "clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" +expecteof "valgrind $clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" new "netconf edit config" -expecteof "clixon_netconf -qf $clixon_cf" "eth0eth1true
9.2.3.424
]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf" "eth0eth1true
9.2.3.424
]]>]]>" "^]]>]]>$" new "netconf get config xpath" -expecteof "clixon_netconf -qf $clixon_cf" "]]>]]>" "^true]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^true]]>]]>$" new "netconf validate missing type" -expecteof "clixon_netconf -qf $clixon_cf" "]]>]]>" "^" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^" new "netconf discard-changes" -expecteof "clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" new "netconf get empty config2" -expecteof "clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" -new "netconf edit config patch" -expecteof "clixon_netconf -qf $clixon_cf" "eth1eth]]>]]>" "^]]>]]>$" +new "netconf edit config eth1" +expecteof "$clixon_netconf -qf $clixon_cf" "eth1eth]]>]]>" "^]]>]]>$" new "netconf validate" -expecteof "clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" new "netconf commit" -expecteof "clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" new "netconf lock/unlock" -expecteof "clixon_netconf -qf $clixon_cf" "]]>]]>]]>]]>" "^]]>]]>]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>]]>]]>" "^]]>]]>]]>]]>$" new "netconf lock/lock" -expecteof "clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" new "netconf lock" -expecteof "clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" new "close-session" -expecteof "clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" new "kill-session" -expecteof "clixon_netconf -qf $clixon_cf" "44]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf" "44]]>]]>" "^]]>]]>$" new "copy startup" -expecteof "clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" new "netconf get startup" -expecteof "clixon_netconf -qf $clixon_cf" "]]>]]>" "^eth1ethtrue]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^eth1ethtrue]]>]]>$" new "netconf delete startup" -expecteof "clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" new "netconf check empty startup" -expecteof "clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" new "netconf subscription" -expectwait "clixon_netconf -qf $clixon_cf" "ROUTING]]>]]>" "^]]>]]>Routing notification]]>]]>$" 30 +expectwait "$clixon_netconf -qf $clixon_cf" "ROUTING]]>]]>" "^]]>]]>Routing notification]]>]]>$" 30 new "Kill backend" # Check if still alive diff --git a/test/test3.sh b/test/test3.sh index b106bfdb..82f63b32 100755 --- a/test/test3.sh +++ b/test/test3.sh @@ -6,7 +6,7 @@ # kill old backend (if any) new "kill old backend" -sudo clixon_backend -zf $clixon_cf +#sudo clixon_backend -zf $clixon_cf if [ $? -ne 0 ]; then err fi @@ -24,6 +24,9 @@ sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c sleep 1 +new "restconf options" +expectfn "curl -i -s -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE" + new "restconf get empty config" expectfn "curl -sG http://localhost/restconf/data" "^null $" @@ -34,8 +37,12 @@ new "restconf get config" expectfn "curl -sG http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth1","type": "eth","enabled": "true"},{ "name": "eth0","type": "eth","enabled": "true"}\]}} $' +new "restconf head" +expectfn "curl -s -I http://localhost/restconf/data" "Content-Type: application/yang.data\+json" + new "restconf patch config" -expectfn 'curl -sX POST -d {"type":"type"} http://localhost/restconf/data/interfaces/interface=eth4' "" +expectfn 'curl -sX POST -d {"type":"eth"} http://localhost/restconf/data/interfaces/interface=eth4' "" +# XXX POST/PUT/PATCH new "restconf delete config" expectfn 'curl -sX DELETE http://localhost/restconf/data/interfaces/interface=eth0' "" @@ -45,7 +52,7 @@ expectfn "curl -sG http://localhost/restconf/data" '{"interfaces": {"interface": $' new "Kill restconf daemon" -sudo pkill -u www-data clixon_restconf +#sudo pkill -u www-data clixon_restconf new "Kill backend" # Check if still alive