diff --git a/apps/netconf/netconf_plugin.c b/apps/netconf/netconf_plugin.c index fc127ced..926c195f 100644 --- a/apps/netconf/netconf_plugin.c +++ b/apps/netconf/netconf_plugin.c @@ -53,8 +53,7 @@ #include "netconf_lib.h" #include "netconf_plugin.h" -/* - * Unload a plugin +/*! Unload a plugin */ static int plugin_unload(clicon_handle h, void *handle) @@ -112,9 +111,7 @@ static int nplugins = 0; static plghndl_t *plugins = NULL; static netconf_reg_t *deps = NULL; -/* - * netconf_plugin_load - * Load allplugins you can find in CLICON_NETCONF_DIR +/*! Load all plugins you can find in CLICON_NETCONF_DIR */ int netconf_plugin_load(clicon_handle h) @@ -160,6 +157,7 @@ quit: return retval; } +/*! Unload all netconf plugins */ int netconf_plugin_unload(clicon_handle h) { @@ -180,8 +178,7 @@ netconf_plugin_unload(clicon_handle h) return 0; } -/* - * Call plugin_start in all plugins +/*! Call plugin_start in all plugins */ int netconf_plugin_start(clicon_handle h, int argc, char **argv) @@ -203,8 +200,7 @@ netconf_plugin_start(clicon_handle h, int argc, char **argv) } -/* - * netconf_register_callback +/*! Register netconf callback * Called from plugin to register a callback for a specific netconf XML tag. */ int diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 0c7628f6..aaa2e276 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -230,3 +231,143 @@ readdata(FCGX_Request *r) return cb; } +static int nplugins = 0; +static plghndl_t *plugins = NULL; + +/*! Load a dynamic plugin object and call it's init-function + * Note 'file' may be destructively modified + */ +static plghndl_t +plugin_load (clicon_handle h, + char *file, + int dlflags, + const char *cnklbl) +{ + char *error; + void *handle = NULL; + plginit_t *initfn; + + dlerror(); /* Clear any existing error */ + if ((handle = dlopen (file, dlflags)) == NULL) { + error = (char*)dlerror(); + clicon_err(OE_PLUGIN, errno, "dlopen: %s\n", error ? error : "Unknown error"); + goto quit; + } + /* call plugin_init() if defined */ + if ((initfn = dlsym(handle, PLUGIN_INIT)) != NULL) { + if (initfn(h) != 0) { + clicon_err(OE_PLUGIN, errno, "Failed to initiate %s\n", strrchr(file,'/')?strchr(file, '/'):file); + goto quit; + } + } +quit: + + return handle; +} + + +/*! Load all plugins you can find in CLICON_RESTCONF_DIR + */ +int +restconf_plugin_load(clicon_handle h) +{ + int retval = -1; + char *dir; + int ndp; + struct dirent *dp; + int i; + char *filename; + plghndl_t *handle; + + if ((dir = clicon_restconf_dir(h)) == NULL){ + clicon_err(OE_PLUGIN, 0, "clicon_restconf_dir not defined"); + goto quit; + } + /* Get plugin objects names from plugin directory */ + if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG, __FUNCTION__))<0) + goto quit; + + /* Load all plugins */ + for (i = 0; i < ndp; i++) { + filename = chunk_sprintf(__FUNCTION__, "%s/%s", dir, dp[i].d_name); + clicon_debug(1, "DEBUG: Loading plugin '%.*s' ...", + (int)strlen(filename), filename); + if (filename == NULL) { + clicon_err(OE_UNIX, errno, "chunk"); + goto quit; + } + if ((handle = plugin_load (h, filename, RTLD_NOW, __FUNCTION__)) == NULL) + goto quit; + if ((plugins = rechunk(plugins, (nplugins+1) * sizeof (*plugins), NULL)) == NULL) { + clicon_err(OE_UNIX, errno, "chunk"); + goto quit; + } + plugins[nplugins++] = handle; + unchunk (filename); + } + retval = 0; +quit: + unchunk_group(__FUNCTION__); + return retval; +} + +/*! Unload a plugin + */ +static int +plugin_unload(clicon_handle h, void *handle) +{ + int retval = 0; + char *error; + plgexit_t *exitfn; + + /* Call exit function is it exists */ + exitfn = dlsym(handle, PLUGIN_EXIT); + if (dlerror() == NULL) + exitfn(h); + + dlerror(); /* Clear any existing error */ + if (dlclose(handle) != 0) { + error = (char*)dlerror(); + clicon_err(OE_PLUGIN, errno, "dlclose: %s\n", error ? error : "Unknown error"); + /* Just report */ + } + return retval; +} + +/*! Unload all restconf plugins */ +int +restconf_plugin_unload(clicon_handle h) +{ + int i; + + for (i = 0; i < nplugins; i++) + plugin_unload(h, plugins[i]); + if (plugins) + unchunk(plugins); + nplugins = 0; + return 0; +} + +/*! Call plugin_start in all plugins + */ +int +restconf_plugin_start(clicon_handle h, + int argc, + char **argv) +{ + int i; + plgstart_t *startfn; + + for (i = 0; i < nplugins; i++) { + /* Call exit function is it exists */ + if ((startfn = dlsym(plugins[i], PLUGIN_START)) == NULL) + break; + optind = 0; + if (startfn(h, argc, argv) < 0) { + clicon_debug(1, "plugin_start() failed\n"); + return -1; + } + } + return 0; +} + diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index b9b1ee08..d6b5c5f8 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -30,4 +30,10 @@ int str2cvec(char *string, char delim1, char delim2, cvec **cvp); int test(FCGX_Request *r, int dbg); cbuf *readdata(FCGX_Request *r); + +int restconf_plugin_load(clicon_handle h); +int restconf_plugin_start(clicon_handle h, int argc, char **argv); +int restconf_plugin_unload(clicon_handle h); + + #endif /* _RESTCONF_LIB_H_ */ diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index ebac125a..2820a5f0 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -56,17 +56,47 @@ #include "restconf_lib.h" /* Command line options to be passed to getopt(3) */ -#define RESTCONF_OPTS "hDf:" +#define RESTCONF_OPTS "hDf:p:" /* Should be discovered via "/.well-known/host-meta" 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] 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 @@ -172,19 +202,17 @@ api_data_get(clicon_handle h, cprintf(path1, "/%s", name); } } - clicon_debug(1, "path:%s", cbuf_get(path)); - clicon_debug(1, "path1:%s", cbuf_get(path1)); if (xmldb_get(h, "running", cbuf_get(path), 0, &xt, &vec, &veclen) < 0) goto done; FCGX_SetExitStatus(200, r->out); /* OK */ - - FCGX_FPrintF(r->out, "Content-Type: application/yang.data+xml\r\n"); + FCGX_FPrintF(r->out, "Content-Type: application/yang.data+json\r\n"); FCGX_FPrintF(r->out, "\r\n"); if ((cbx = cbuf_new()) == NULL) - goto done; + goto done; if (xml2json_cbuf(cbx, xt, 1, 0) < 0) goto done; - FCGX_FPrintF(r->out, "%s\r\n", cbuf_get(cbx)); + FCGX_FPrintF(r->out, "%s", cbuf_get(cbx)); + FCGX_FPrintF(r->out, "hej\r\n\r\n"); retval = 0; done: if (vec) @@ -241,7 +269,7 @@ api_data_delete(clicon_handle h, return retval; } -/*! Generic REST PUT method +/*! 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]) @@ -249,17 +277,23 @@ api_data_delete(clicon_handle h, * @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. - * Problem: we have URI that defines a path (eg "interface/name=eth1") and data - * which defines a tree from that point. - * But, xmldb api can only do either - * - xmldb_put() with a complete xml-tree, or - * - xmldb_put_xkey for path and key value - * What we need is path and sub-xml tree. - * Alt1: parse URI to XML and and call xmldb_put() - * Alt2: Extend xmldb API with path + xml-tree. + 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, @@ -268,7 +302,8 @@ api_data_put(clicon_handle h, cvec *pcvec, int pi, cvec *qvec, - char *data) + char *data, + int post) { int retval = -1; int i; @@ -304,7 +339,7 @@ api_data_put(clicon_handle h, if (clicon_rpc_commit(h, "candidate", "running", 0, 0) < 0) goto done; - FCGX_SetExitStatus(201, r->out); + FCGX_SetExitStatus(201, r->out); /* Created */ FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n"); FCGX_FPrintF(r->out, "\r\n"); done: @@ -339,10 +374,14 @@ api_data(clicon_handle h, clicon_debug(1, "%s", __FUNCTION__); request_method = FCGX_GetParam("REQUEST_METHOD", r->envp); - if (strcmp(request_method, "GET")==0) + if (strcmp(request_method, "OPTIONS")==0) + retval = api_data_options(h, r, pcvec, pi, qvec, 0); + 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); + 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); else if (strcmp(request_method, "DELETE")==0) retval = api_data_delete(h, r, api_path, pi); else @@ -389,6 +428,7 @@ request_process(clicon_handle h, method = pvec[2]; retval = 0; test(r, 1); + /* XXX Credentials */ if (strcmp(method, "data") == 0) /* restconf, skip /api/data */ retval = api_data(h, r, path, pcvec, 2, qvec, data); else if (strcmp(method, "test") == 0) @@ -405,6 +445,7 @@ request_process(clicon_handle h, if (cb) cbuf_free(cb); unchunk_group(__FUNCTION__); + clicon_debug(1, "%s end", __FUNCTION__); return retval; } @@ -417,13 +458,18 @@ usage(clicon_handle h, char *argv0) { + char *restconfdir = clicon_restconf_dir(h); + fprintf(stderr, "usage:%s [options]\n" "where options are\n" "\t-h \t\tHelp\n" "\t-D \t\tDebug. Log to syslog\n" - "\t-f \tConfiguration file (mandatory)\n", - argv0 + "\t-f \tConfiguration file (mandatory)\n" + "\t-d \tSpecify restconf plugin directory dir (default: %s)\n", + argv0, + restconfdir ); + exit(0); } /*! Main routine for grideye fastcgi API @@ -460,6 +506,11 @@ main(int argc, usage(h, argv[0]); clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg); break; + case 'd': /* Plugin directory */ + if (!strlen(optarg)) + usage(h, argv[0]); + clicon_option_str_set(h, "CLICON_RESTCONF_DIR", optarg); + break; default: usage(h, argv[0]); break; @@ -474,6 +525,10 @@ main(int argc, if (clicon_options_main(h) < 0) goto done; + /* Initialize plugins group */ + if (restconf_plugin_load(h) < 0) + return -1; + /* Parse yang database spec file */ if (yang_spec_main(h, NULL, 0) < 0) goto done; @@ -516,5 +571,6 @@ main(int argc, } retval = 0; done: + restconf_plugin_unload(h); return retval; } diff --git a/clixon.conf.cpp.cpp b/clixon.conf.cpp.cpp index 2ab30581..23ba6802 100644 --- a/clixon.conf.cpp.cpp +++ b/clixon.conf.cpp.cpp @@ -51,6 +51,9 @@ CLICON_BACKEND_DIR libdir/APPNAME/backend # Location of netconf (frontend) .so plugins CLICON_NETCONF_DIR libdir/APPNAME/netconf +# Location of restconf (frontend) .so plugins +CLICON_RESTCONF_DIR libdir/APPNAME/restconf + # Location of cli frontend .so plugins CLICON_CLI_DIR libdir/APPNAME/cli diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index c101469a..df3c6d48 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -80,6 +80,7 @@ char *clicon_backend_dir(clicon_handle h); char *clicon_cli_dir(clicon_handle h); char *clicon_clispec_dir(clicon_handle h); char *clicon_netconf_dir(clicon_handle h); +char *clicon_restconf_dir(clicon_handle h); char *clicon_archive_dir(clicon_handle h); char *clicon_startup_config(clicon_handle h); int clicon_sock_family(clicon_handle h); diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 43307570..abd59b67 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -216,6 +216,10 @@ clicon_option_sanity(clicon_hash_t *copt) clicon_err(OE_UNIX, 0, "CLICON_NETCONF_DIR not defined in config file"); goto done; } + if (!hash_lookup(copt, "CLICON_RESTCONF_DIR")){ + clicon_err(OE_UNIX, 0, "CLICON_RESTCONF_DIR not defined in config file"); + goto done; + } if (!hash_lookup(copt, "CLICON_YANG_DIR")){ clicon_err(OE_UNIX, 0, "CLICON_YANG_DIR not defined in config file"); goto done; @@ -432,6 +436,12 @@ clicon_netconf_dir(clicon_handle h) return clicon_option_str(h, "CLICON_NETCONF_DIR"); } +char * +clicon_restconf_dir(clicon_handle h) +{ + return clicon_option_str(h, "CLICON_RESTCONF_DIR"); +} + char * clicon_archive_dir(clicon_handle h) {