* Pushed tag to 4.0.1.PRE

* Restconf RFC 8040 increased feature compliance
  * Cache-Control: no-cache added in HTTP responses (RFC Section 5.5)
  * Restconf monitoring capabilities (RFC Section 9.1)
* Added support for Yang extensions
  * New plugin callback: ca_extension
  * Main backend example includes example code on how to implement a Yang extension in a plugin.
* JSON changes
  * Non-pretty-print output removed all extra spaces.
    * Example: `{"nacm-example:x": 42}` --> {"nacm-example:x":42}`
  * Empty JSON container changed from `null` to `{}`.
    * Empty list and leafs remain as `null`
* Removed unnecessary configure dependencies
  * libnsl, libcrypt, libm, if_vlan,...
* pseudo-plugin added, to enable callbacks also for main programs. Useful for extensions
* Yang Unique statements with multiple schema identifiers did not work on some platforms due to memory error.
This commit is contained in:
Olof hagsand 2019-07-23 22:11:14 +02:00
parent fe46a0e093
commit e7b60619da
60 changed files with 1619 additions and 568 deletions

View file

@ -61,7 +61,7 @@ CLIXON_LIB = libclixon$(SH_SUFFIX).$(CLIXON_MAJOR).$(CLIXON_MINOR)
# even though it may exist in $(libdir). But the new version may not have been installed yet.
LIBDEPS = $(top_srcdir)/lib/src/$(CLIXON_LIB)
LIBS = -L$(top_srcdir)/lib/src @LIBS@ $(top_srcdir)/lib/src/$(CLIXON_LIB) -lpthread
LIBS = -L$(top_srcdir)/lib/src @LIBS@ $(top_srcdir)/lib/src/$(CLIXON_LIB)
CPPFLAGS = @CPPFLAGS@ -fPIC
INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@

View file

@ -149,6 +149,37 @@ backend_client_rm(clicon_handle h,
return backend_client_delete(h, ce); /* actually purge it */
}
/*!
* Maybe should be in the restconf client instead of backend?
* @param[in] h Clicon handle
* @param[in] yspec Yang spec
* @param[in] xpath Xpath selection, not used but may be to filter early
* @param[out] xrs XML restconf-state node
* @see netconf_create_hello
* @see rfc8040 Sections 9.1
*/
static int
client_get_capabilities(clicon_handle h,
yang_stmt *yspec,
char *xpath,
cxobj **xret)
{
int retval = -1;
cxobj *xrstate = NULL; /* xml restconf-state node */
cxobj *xcap = NULL; /* xml capabilities node */
if ((xrstate = xpath_first(*xret, "restconf-state")) == NULL){
clicon_err(OE_YANG, ENOENT, "restconf-state not found in config node");
goto done;
}
if ((xcap = xml_new("capabilities", xrstate, yspec)) == NULL)
goto done;
if (xml_parse_va(&xcap, yspec, "<capability>urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit</capability>") < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Get streams state according to RFC 8040 or RFC5277 common function
* @param[in] h Clicon handle
@ -165,23 +196,18 @@ static int
client_get_streams(clicon_handle h,
yang_stmt *yspec,
char *xpath,
char *module,
yang_stmt *ymod,
char *top,
cxobj **xret)
{
int retval = -1;
yang_stmt *ystream = NULL; /* yang stream module */
yang_stmt *yns = NULL; /* yang namespace */
cxobj *x = NULL;
cbuf *cb = NULL;
int ret;
if ((ystream = yang_find(yspec, Y_MODULE, module)) == NULL){
clicon_err(OE_YANG, 0, "%s yang module not found", module);
goto done;
}
if ((yns = yang_find(ystream, Y_NAMESPACE, NULL)) == NULL){
clicon_err(OE_YANG, 0, "%s yang namespace not found", module);
if ((yns = yang_find(ymod, Y_NAMESPACE, NULL)) == NULL){
clicon_err(OE_YANG, 0, "%s yang namespace not found", yang_argument_get(ymod));
goto done;
}
if ((cb = cbuf_new()) == NULL){
@ -189,6 +215,9 @@ client_get_streams(clicon_handle h,
goto done;
}
cprintf(cb,"<%s xmlns=\"%s\">", top, yang_argument_get(yns));
/* Second argument is a hack to have the same function for the
* RFC5277 and 8040 stream cases
*/
if (stream_get_xml(h, strcmp(top,"restconf-state")==0, cb) < 0)
goto done;
cprintf(cb,"</%s>", top);
@ -234,23 +263,47 @@ client_statedata(clicon_handle h,
size_t xlen;
int i;
yang_stmt *yspec;
yang_stmt *ymod;
int ret;
char *namespace;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277")){
if ((ret = client_get_streams(h, yspec, xpath, "clixon-rfc5277", "netconf", xret)) < 0)
if ((ymod = yang_find_module_by_name(yspec, "clixon-rfc5277")) == NULL){
clicon_err(OE_YANG, ENOENT, "yang module clixon-rfc5277 not found");
goto done;
}
if ((namespace = yang_find_mynamespace(ymod)) == NULL){
clicon_err(OE_YANG, ENOENT, "clixon-rfc5277 namespace not found");
goto done;
}
if (xml_parse_va(xret, yspec, "<netconf xmlns=\"%s\"/>", namespace) < 0)
goto done;
if ((ret = client_get_streams(h, yspec, xpath, ymod, "netconf", xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040")){
if ((ret = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) < 0)
if ((ymod = yang_find_module_by_name(yspec, "ietf-restconf-monitoring")) == NULL){
clicon_err(OE_YANG, ENOENT, "yang module ietf-restconf-monitoring not found");
goto done;
}
if ((namespace = yang_find_mynamespace(ymod)) == NULL){
clicon_err(OE_YANG, ENOENT, "ietf-restconf-monitoring namespace not found");
goto done;
}
if (xml_parse_va(xret, yspec, "<restconf-state xmlns=\"%s\"/>", namespace) < 0)
goto done;
if ((ret = client_get_streams(h, yspec, xpath, ymod, "restconf-state", xret)) < 0)
goto done;
if (ret == 0)
goto fail;
if ((ret = client_get_capabilities(h, yspec, xpath, xret)) < 0)
goto done;
}
if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895")){
if ((ret = yang_modules_state_get(h, yspec, xpath, nsc, 0, xret)) < 0)

View file

@ -332,6 +332,7 @@ main(int argc,
cbuf *cbret = NULL; /* startup cbuf if invalid */
enum startup_status status = STARTUP_ERR; /* Startup status */
int ret;
char *dir;
/* In the startup, logs to stderr & syslog and debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
@ -573,6 +574,13 @@ main(int argc,
if ((yspec = yspec_new()) == NULL)
goto done;
clicon_dbspec_yang_set(h, yspec);
/* Load backend plugins before yangs are loaded (eg extension callbacks) */
if ((dir = clicon_backend_dir(h)) != NULL &&
clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir,
clicon_option_str(h, "CLICON_BACKEND_REGEXP")) < 0)
goto done;
/* Load Yang modules
* 1. Load a yang module as a specific absolute filename */
if ((str = clicon_yang_main_file(h)) != NULL)
@ -596,6 +604,9 @@ main(int argc,
/* Add netconf yang spec, used by netconf client and as internal protocol */
if (netconf_module_load(h) < 0)
goto done;
/* Load yang restconf module */
if (yang_spec_parse_module(h, "ietf-restconf", NULL, yspec)< 0)
goto done;
/* Load yang Restconf stream discovery */
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") &&
yang_spec_parse_module(h, "ietf-restconf-monitoring", NULL, yspec)< 0)
@ -646,8 +657,6 @@ main(int argc,
goto done;
case SM_NONE: /* Fall through *
* Load plugins and call plugin_init() */
if (backend_plugin_initiate(h) != 0)
goto done;
status = STARTUP_OK;
break;
case SM_RUNNING: /* Use running as startup */
@ -735,7 +744,6 @@ main(int argc,
clicon_err(OE_DEMON, errno, "Setting signal");
goto done;
}
/* Initialize server socket and save it to handle */
if ((ss = backend_server_socket(h)) < 0)
goto done;

View file

@ -63,23 +63,6 @@
#include "backend_plugin.h"
#include "backend_commit.h"
/*! Load a plugin group.
* @param[in] h Clicon handle
* @retval 0 OK
* @retval -1 Error
*/
int
backend_plugin_initiate(clicon_handle h)
{
char *dir;
/* Load application plugins */
if ((dir = clicon_backend_dir(h)) == NULL)
return 0;
return clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir,
clicon_option_str(h, "CLICON_BACKEND_REGEXP"));
}
/*! Request plugins to reset system state
* The system 'state' should be the same as the contents of running_db
* @param[in] h Clicon handle

View file

@ -67,8 +67,6 @@ typedef struct {
/*
* Prototypes
*/
int backend_plugin_initiate(clicon_handle h);
int clixon_plugin_reset(clicon_handle h, char *db);
int clixon_plugin_statedata(clicon_handle h, yang_stmt *yspec, cvec *nsc,

View file

@ -138,9 +138,6 @@ startup_mode_startup(clicon_handle h,
clicon_err(OE_FATAL, 0, "Invalid startup db: %s", db);
goto done;
}
/* Load plugins and call plugin_init() */
if (backend_plugin_initiate(h) != 0)
goto done;
/* If startup does not exist, create it empty */
if (xmldb_exists(h, db) != 1){ /* diff */
if (xmldb_create(h, db) < 0) /* diff */

View file

@ -47,9 +47,6 @@
#include <ctype.h>
#include <unistd.h>
#ifdef HAVE_CRYPT_H
#include <crypt.h>
#endif
#include <dirent.h>
#include <syslog.h>
#include <arpa/inet.h>

View file

@ -284,6 +284,7 @@ main(int argc, char **argv)
struct passwd *pw;
char *str;
int tabmode;
char *dir;
/* Defaults */
once = 0;
@ -466,6 +467,11 @@ main(int argc, char **argv)
*/
cv_exclude_keys(clicon_cli_varonly(h));
/* Load cli plugins before yangs are loaded (eg extension callbacks) */
if ((dir = clicon_cli_dir(h)) != NULL &&
clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
goto done;
/* Create top-level and store as option */
if ((yspec = yspec_new()) == NULL)
goto done;

View file

@ -319,7 +319,6 @@ int
cli_syntax_load(clicon_handle h)
{
int retval = -1;
char *plugin_dir = NULL;
char *clispec_dir = NULL;
char *clispec_file = NULL;
int ndp;
@ -336,7 +335,6 @@ cli_syntax_load(clicon_handle h)
return 0;
/* Format plugin directory path */
plugin_dir = clicon_cli_dir(h);
clispec_dir = clicon_clispec_dir(h);
clispec_file = clicon_option_str(h, "CLICON_CLISPEC_FILE");
@ -349,10 +347,6 @@ cli_syntax_load(clicon_handle h)
cli_syntax_set(h, stx);
/* Load cli plugins */
if (plugin_dir &&
clixon_plugins_load(h, CLIXON_PLUGIN_INIT, plugin_dir, NULL)< 0)
goto done;
if (clispec_file){
if (cli_load_syntax(h, clispec_file, NULL) < 0)
goto done;

View file

@ -48,9 +48,6 @@
#include <ctype.h>
#include <unistd.h>
#ifdef HAVE_CRYPT_H
#include <crypt.h>
#endif
#include <dirent.h>
#include <syslog.h>
#include <arpa/inet.h>

View file

@ -488,6 +488,12 @@ main(int argc,
if ((yspec = yspec_new()) == NULL)
goto done;
clicon_dbspec_yang_set(h, yspec);
/* Load netconf plugins before yangs are loaded (eg extension callbacks) */
if ((dir = clicon_netconf_dir(h)) != NULL &&
clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
goto done;
/* Load Yang modules
* 1. Load a yang module as a specific absolute filename */
if ((str = clicon_yang_main_file(h)) != NULL){
@ -514,15 +520,9 @@ main(int argc,
/* Add netconf yang spec, used by netconf client and as internal protocol */
if (netconf_module_load(h) < 0)
goto done;
/* Initialize plugins group */
if ((dir = clicon_netconf_dir(h)) != NULL)
if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
goto done;
/* Call start function is all plugins before we go interactive */
if (clixon_plugin_start(h) < 0)
goto done;
if (!quiet)
send_hello(h, 1);
if (event_reg_fd(0, netconf_input_cb, h, "netconf socket") < 0)

View file

@ -464,7 +464,7 @@ api_return_err(clicon_handle h,
}
else{
FCGX_FPrintF(r->out, "{");
FCGX_FPrintF(r->out, "\"ietf-restconf:errors\" : ");
FCGX_FPrintF(r->out, "\"ietf-restconf:errors\":");
FCGX_FPrintF(r->out, "%s", cbuf_get(cb));
FCGX_FPrintF(r->out, "}\r\n");
}

View file

@ -190,6 +190,7 @@ api_well_known(clicon_handle h,
FCGX_Request *r)
{
clicon_debug(1, "%s", __FUNCTION__);
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
FCGX_FPrintF(r->out, "Content-Type: application/xrd+xml\r\n");
FCGX_FPrintF(r->out, "\r\n");
FCGX_SetExitStatus(200, r->out); /* OK */
@ -210,24 +211,32 @@ static int
api_root(clicon_handle h,
FCGX_Request *r)
{
int retval = -1;
char *media_accept;
int use_xml = 0; /* By default use JSON */
cxobj *xt = NULL;
cbuf *cb = NULL;
int pretty;
int retval = -1;
char *media_accept;
int use_xml = 0; /* By default use JSON */
cxobj *xt = NULL;
cbuf *cb = NULL;
int pretty;
yang_stmt *yspec;
clicon_debug(1, "%s", __FUNCTION__);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp);
if (strcmp(media_accept, "application/yang-data+xml")==0)
use_xml++;
clicon_debug(1, "%s use-xml:%d media-accept:%s", __FUNCTION__, use_xml, media_accept);
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
FCGX_FPrintF(r->out, "\r\n");
if (xml_parse_string("<restconf><data></data><operations></operations><yang-library-version>2016-06-21</yang-library-version></restconf>", NULL, &xt) < 0)
if (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>", NULL, &xt) < 0)
goto done;
if (xml_apply(xt, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
@ -274,6 +283,7 @@ api_yang_library_version(clicon_handle h,
if (strcmp(media_accept, "application/yang-data+xml")==0)
use_xml++;
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
FCGX_FPrintF(r->out, "\r\n");
if (xml_parse_va(&xt, NULL, "<yang-library-version>%s</yang-library-version>", ietf_yang_library_revision) < 0)
@ -467,6 +477,41 @@ restconf_sig_term(int arg)
exit(-1);
}
/*! Callback for yang extensions ietf-restconf:yang-data
* @see ietf-restconf.yang
* @param[in] h Clixon handle
* @param[in] yext Yang node of extension
* @param[in] ys Yang node of (unknown) statement belonging to extension
* @retval 0 OK, all callbacks executed OK
* @retval -1 Error in one callback
*/
static int
restconf_main_extension_cb(clicon_handle h,
yang_stmt *yext,
yang_stmt *ys)
{
int retval = -1;
char *extname;
char *modname;
yang_stmt *ymod;
yang_stmt *yc;
ymod = ys_module(yext);
modname = yang_argument_get(ymod);
extname = yang_argument_get(yext);
if (strcmp(modname, "ietf-restconf") != 0 || strcmp(extname, "yang-data") != 0)
goto ok;
clicon_debug(1, "%s Enabled extension:%s:%s", __FUNCTION__, modname, extname);
if ((yc = ys_prune(ys, 0)) == NULL)
goto done;
if (yn_insert(yang_parent_get(ys), yc) < 0)
goto done;
ok:
retval = 0;
done:
return retval;
}
static void
restconf_sig_child(int arg)
{
@ -510,22 +555,23 @@ int
main(int argc,
char **argv)
{
int retval = -1;
int sock;
char *argv0 = argv[0];
FCGX_Request request;
FCGX_Request *r = &request;
int c;
char *sockpath;
char *path;
clicon_handle h;
char *dir;
int logdst = CLICON_LOG_SYSLOG;
yang_stmt *yspec = NULL;
yang_stmt *yspecfg = NULL; /* For config XXX clixon bug */
char *stream_path;
int finish;
char *str;
int retval = -1;
int sock;
char *argv0 = argv[0];
FCGX_Request request;
FCGX_Request *r = &request;
int c;
char *sockpath;
char *path;
clicon_handle h;
char *dir;
int logdst = CLICON_LOG_SYSLOG;
yang_stmt *yspec = NULL;
yang_stmt *yspecfg = NULL; /* For config XXX clixon bug */
char *stream_path;
int finish;
char *str;
clixon_plugin *cp = NULL;
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
@ -635,15 +681,21 @@ main(int argc,
/* Access the remaining argv/argc options (after --) w clicon-argv_get() */
clicon_argv_set(h, argv0, argc, argv);
/* Initialize plugins group */
if ((dir = clicon_restconf_dir(h)) != NULL)
if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
return -1;
/* Create top-level yang spec and store as option */
if ((yspec = yspec_new()) == NULL)
goto done;
clicon_dbspec_yang_set(h, yspec);
clicon_dbspec_yang_set(h, yspec);
/* Load restconf plugins before yangs are loaded (eg extension callbacks) */
if ((dir = clicon_restconf_dir(h)) != NULL)
if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
return -1;
/* Create a pseudo-plugin to create extension callback to set the ietf-routing
* yang-data extension for api-root top-level restconf function.
*/
if (clixon_pseudo_plugin(h, "pseudo restconf", &cp) < 0)
goto done;
cp->cp_api.ca_extension = restconf_main_extension_cb;
/* Load Yang modules
* 1. Load a yang module as a specific absolute filename */
@ -669,6 +721,10 @@ main(int argc,
if (yang_modules_init(h) < 0)
goto done;
/* Load yang restconf module */
if (yang_spec_parse_module(h, "ietf-restconf", NULL, yspec)< 0)
goto done;
/* Add netconf yang spec, used as internal protocol */
if (netconf_module_load(h) < 0)
goto done;

View file

@ -326,6 +326,7 @@ api_data_get2(clicon_handle h,
}
clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx));
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
FCGX_FPrintF(r->out, "\r\n");
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
@ -663,7 +664,6 @@ api_data_post(clicon_handle h,
clicon_log(LOG_WARNING, "%s: copy-config running->startup failed", __FUNCTION__);
}
}
FCGX_SetExitStatus(201, r->out); /* Created */
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");