restconf plugins

This commit is contained in:
Olof hagsand 2016-09-23 12:36:40 +02:00
parent b82a8bae26
commit 64f197cb00
7 changed files with 247 additions and 34 deletions

View file

@ -53,8 +53,7 @@
#include "netconf_lib.h" #include "netconf_lib.h"
#include "netconf_plugin.h" #include "netconf_plugin.h"
/* /*! Unload a plugin
* Unload a plugin
*/ */
static int static int
plugin_unload(clicon_handle h, void *handle) plugin_unload(clicon_handle h, void *handle)
@ -112,9 +111,7 @@ static int nplugins = 0;
static plghndl_t *plugins = NULL; static plghndl_t *plugins = NULL;
static netconf_reg_t *deps = NULL; static netconf_reg_t *deps = NULL;
/* /*! Load all plugins you can find in CLICON_NETCONF_DIR
* netconf_plugin_load
* Load allplugins you can find in CLICON_NETCONF_DIR
*/ */
int int
netconf_plugin_load(clicon_handle h) netconf_plugin_load(clicon_handle h)
@ -160,6 +157,7 @@ quit:
return retval; return retval;
} }
/*! Unload all netconf plugins */
int int
netconf_plugin_unload(clicon_handle h) netconf_plugin_unload(clicon_handle h)
{ {
@ -180,8 +178,7 @@ netconf_plugin_unload(clicon_handle h)
return 0; return 0;
} }
/* /*! Call plugin_start in all plugins
* Call plugin_start in all plugins
*/ */
int int
netconf_plugin_start(clicon_handle h, int argc, char **argv) netconf_plugin_start(clicon_handle h, int argc, char **argv)
@ -203,8 +200,7 @@ netconf_plugin_start(clicon_handle h, int argc, char **argv)
} }
/* /*! Register netconf callback
* netconf_register_callback
* Called from plugin to register a callback for a specific netconf XML tag. * Called from plugin to register a callback for a specific netconf XML tag.
*/ */
int int

View file

@ -12,6 +12,7 @@
#include <time.h> #include <time.h>
#include <fcgi_stdio.h> #include <fcgi_stdio.h>
#include <signal.h> #include <signal.h>
#include <dlfcn.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <curl/curl.h> #include <curl/curl.h>
@ -230,3 +231,143 @@ readdata(FCGX_Request *r)
return cb; 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;
}

View file

@ -30,4 +30,10 @@ int str2cvec(char *string, char delim1, char delim2, cvec **cvp);
int test(FCGX_Request *r, int dbg); int test(FCGX_Request *r, int dbg);
cbuf *readdata(FCGX_Request *r); 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_ */ #endif /* _RESTCONF_LIB_H_ */

View file

@ -56,17 +56,47 @@
#include "restconf_lib.h" #include "restconf_lib.h"
/* Command line options to be passed to getopt(3) */ /* 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" /* Should be discovered via "/.well-known/host-meta"
resource ([RFC6415]) */ resource ([RFC6415]) */
#define RESTCONF_API_ROOT "/restconf/" #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 /*! Generic REST GET method
* According to restconf (Sec 3.5.1.1 in [draft]) * According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] r Fastcgi request handle * @param[in] r Fastcgi request handle
* @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
* @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] qvec Vector of query string (QUERY_STRING)
* @code * @code
@ -172,19 +202,17 @@ api_data_get(clicon_handle h,
cprintf(path1, "/%s", name); 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) if (xmldb_get(h, "running", cbuf_get(path), 0, &xt, &vec, &veclen) < 0)
goto done; goto done;
FCGX_SetExitStatus(200, r->out); /* OK */ FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: application/yang.data+json\r\n");
FCGX_FPrintF(r->out, "Content-Type: application/yang.data+xml\r\n");
FCGX_FPrintF(r->out, "\r\n"); FCGX_FPrintF(r->out, "\r\n");
if ((cbx = cbuf_new()) == NULL) if ((cbx = cbuf_new()) == NULL)
goto done; goto done;
if (xml2json_cbuf(cbx, xt, 1, 0) < 0) if (xml2json_cbuf(cbx, xt, 1, 0) < 0)
goto done; 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; retval = 0;
done: done:
if (vec) if (vec)
@ -241,7 +269,7 @@ api_data_delete(clicon_handle h,
return retval; return retval;
} }
/*! 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] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft]) * @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] 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 data * @param[in] dvec Stream input data
* @param[in] post POST instead of PUT
* Example: * Example:
curl -X PUT -d {\"enabled\":\"false\"} http://127.0.0.1/restconf/data/interfaces/interface=eth1 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 POST:
* which defines a tree from that point. If the POST method succeeds, a "201 Created" status-line is returned
* But, xmldb api can only do either and there is no response message-body. A "Location" header
* - xmldb_put() with a complete xml-tree, or identifying the child resource that was created MUST be present in
* - xmldb_put_xkey for path and key value the response in this case.
* What we need is path and sub-xml tree.
* Alt1: parse URI to XML and and call xmldb_put() If the data resource already exists, then the POST request MUST fail
* Alt2: Extend xmldb API with path + xml-tree. and a "409 Conflict" status-line MUST be returned.
*/ */
static int static int
api_data_put(clicon_handle h, api_data_put(clicon_handle h,
@ -268,7 +302,8 @@ api_data_put(clicon_handle h,
cvec *pcvec, cvec *pcvec,
int pi, int pi,
cvec *qvec, cvec *qvec,
char *data) char *data,
int post)
{ {
int retval = -1; int retval = -1;
int i; int i;
@ -304,7 +339,7 @@ api_data_put(clicon_handle h,
if (clicon_rpc_commit(h, "candidate", "running", if (clicon_rpc_commit(h, "candidate", "running",
0, 0) < 0) 0, 0) < 0)
goto done; 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, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n"); FCGX_FPrintF(r->out, "\r\n");
done: done:
@ -339,10 +374,14 @@ api_data(clicon_handle h,
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
request_method = FCGX_GetParam("REQUEST_METHOD", r->envp); 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); retval = api_data_get(h, r, pcvec, pi, qvec);
else if (strcmp(request_method, "PUT")==0) 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) else if (strcmp(request_method, "DELETE")==0)
retval = api_data_delete(h, r, api_path, pi); retval = api_data_delete(h, r, api_path, pi);
else else
@ -389,6 +428,7 @@ request_process(clicon_handle h,
method = pvec[2]; method = pvec[2];
retval = 0; retval = 0;
test(r, 1); test(r, 1);
/* XXX Credentials */
if (strcmp(method, "data") == 0) /* restconf, skip /api/data */ if (strcmp(method, "data") == 0) /* restconf, skip /api/data */
retval = api_data(h, r, path, pcvec, 2, qvec, data); retval = api_data(h, r, path, pcvec, 2, qvec, data);
else if (strcmp(method, "test") == 0) else if (strcmp(method, "test") == 0)
@ -405,6 +445,7 @@ request_process(clicon_handle h,
if (cb) if (cb)
cbuf_free(cb); cbuf_free(cb);
unchunk_group(__FUNCTION__); unchunk_group(__FUNCTION__);
clicon_debug(1, "%s end", __FUNCTION__);
return retval; return retval;
} }
@ -417,13 +458,18 @@ usage(clicon_handle h,
char *argv0) char *argv0)
{ {
char *restconfdir = clicon_restconf_dir(h);
fprintf(stderr, "usage:%s [options]\n" fprintf(stderr, "usage:%s [options]\n"
"where options are\n" "where options are\n"
"\t-h \t\tHelp\n" "\t-h \t\tHelp\n"
"\t-D \t\tDebug. Log to syslog\n" "\t-D \t\tDebug. Log to syslog\n"
"\t-f <file>\tConfiguration file (mandatory)\n", "\t-f <file>\tConfiguration file (mandatory)\n"
argv0 "\t-d <dir>\tSpecify restconf plugin directory dir (default: %s)\n",
argv0,
restconfdir
); );
exit(0);
} }
/*! Main routine for grideye fastcgi API /*! Main routine for grideye fastcgi API
@ -460,6 +506,11 @@ main(int argc,
usage(h, argv[0]); usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg); clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
break; break;
case 'd': /* Plugin directory */
if (!strlen(optarg))
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_RESTCONF_DIR", optarg);
break;
default: default:
usage(h, argv[0]); usage(h, argv[0]);
break; break;
@ -474,6 +525,10 @@ main(int argc,
if (clicon_options_main(h) < 0) if (clicon_options_main(h) < 0)
goto done; goto done;
/* Initialize plugins group */
if (restconf_plugin_load(h) < 0)
return -1;
/* Parse yang database spec file */ /* Parse yang database spec file */
if (yang_spec_main(h, NULL, 0) < 0) if (yang_spec_main(h, NULL, 0) < 0)
goto done; goto done;
@ -516,5 +571,6 @@ main(int argc,
} }
retval = 0; retval = 0;
done: done:
restconf_plugin_unload(h);
return retval; return retval;
} }

View file

@ -51,6 +51,9 @@ CLICON_BACKEND_DIR libdir/APPNAME/backend
# Location of netconf (frontend) .so plugins # Location of netconf (frontend) .so plugins
CLICON_NETCONF_DIR libdir/APPNAME/netconf CLICON_NETCONF_DIR libdir/APPNAME/netconf
# Location of restconf (frontend) .so plugins
CLICON_RESTCONF_DIR libdir/APPNAME/restconf
# Location of cli frontend .so plugins # Location of cli frontend .so plugins
CLICON_CLI_DIR libdir/APPNAME/cli CLICON_CLI_DIR libdir/APPNAME/cli

View file

@ -80,6 +80,7 @@ char *clicon_backend_dir(clicon_handle h);
char *clicon_cli_dir(clicon_handle h); char *clicon_cli_dir(clicon_handle h);
char *clicon_clispec_dir(clicon_handle h); char *clicon_clispec_dir(clicon_handle h);
char *clicon_netconf_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_archive_dir(clicon_handle h);
char *clicon_startup_config(clicon_handle h); char *clicon_startup_config(clicon_handle h);
int clicon_sock_family(clicon_handle h); int clicon_sock_family(clicon_handle h);

View file

@ -216,6 +216,10 @@ clicon_option_sanity(clicon_hash_t *copt)
clicon_err(OE_UNIX, 0, "CLICON_NETCONF_DIR not defined in config file"); clicon_err(OE_UNIX, 0, "CLICON_NETCONF_DIR not defined in config file");
goto done; 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")){ if (!hash_lookup(copt, "CLICON_YANG_DIR")){
clicon_err(OE_UNIX, 0, "CLICON_YANG_DIR not defined in config file"); clicon_err(OE_UNIX, 0, "CLICON_YANG_DIR not defined in config file");
goto done; goto done;
@ -432,6 +436,12 @@ clicon_netconf_dir(clicon_handle h)
return clicon_option_str(h, "CLICON_NETCONF_DIR"); return clicon_option_str(h, "CLICON_NETCONF_DIR");
} }
char *
clicon_restconf_dir(clicon_handle h)
{
return clicon_option_str(h, "CLICON_RESTCONF_DIR");
}
char * char *
clicon_archive_dir(clicon_handle h) clicon_archive_dir(clicon_handle h)
{ {