Merge branch 'develop'

This commit is contained in:
Olof hagsand 2017-07-08 11:13:16 +02:00
commit e4efb6ded0
55 changed files with 2841 additions and 685 deletions

View file

@ -1,10 +1,27 @@
# Clixon CHANGELOG
## 3.3.1
- Added new backend plugin callback: "plugin_statedata()" for retreiving state data
- Added yang dir with ietf-netconf and clixon-config yang specs for internal usage.
- Added state data: Netconf <get> operation introduced; Error when
adding state data in <edit-config>.
- Fixed bug where cli set of leaf-list were doubled, eg cli set foo -> foofoo
- Restricted yang (sub)module file match to match RFC6020 exactly
- Generalized yang type resolution to all included (sub)modules not just the topmost
- Generic map_str2int generic mapping tables
- Removed vector return values from xmldb_get()
## 3.3.1 June 7 2017
- Fixed yang leafref cli completion.
- Removed non-standard api_path extension from internal netconf so that the internal com.
- Removed non-standard api_path extension from the internal netconf protocol so that the internal netcinf is now fully standard.
- Strings in xmldb_put not properly encoded, eg eth/0 became eth.00000

View file

@ -219,10 +219,20 @@ from_client_get_config(clicon_handle h,
clicon_err(OE_XML, 0, "db not found");
goto done;
}
if (xmldb_validate_db(db) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>invalid-value</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>No such database: %s</error-message>"
"</rpc-error></rpc-reply>", db);
goto ok;
}
if ((xfilter = xml_find(xe, "filter")) != NULL)
if ((selector = xml_find_value(xfilter, "select"))==NULL)
selector="/";
if (xmldb_get(h, db, selector, &xret, NULL, NULL) < 0){
if (xmldb_get(h, db, selector, 1, &xret) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>"
"<error-type>application</error-type>"
@ -232,7 +242,6 @@ from_client_get_config(clicon_handle h,
goto ok;
}
cprintf(cbret, "<rpc-reply><data>");
/* if empty only <data/>, if any data then <data><config>..</config></data> */
if (xret!=NULL){
if (xml_child_nr(xret)){
if (xml_name_set(xret, "config") < 0)
@ -250,6 +259,60 @@ from_client_get_config(clicon_handle h,
return retval;
}
/*! Internal message: get
*
* @param[in] h Clicon handle
* @param[in] xe Netconf request xml tree
* @param[out] cbret Return xml value cligen buffer
* @see from_client_get_config
*/
static int
from_client_get(clicon_handle h,
cxobj *xe,
cbuf *cbret)
{
int retval = -1;
cxobj *xfilter;
char *selector = "/";
cxobj *xret = NULL;
if ((xfilter = xml_find(xe, "filter")) != NULL)
if ((selector = xml_find_value(xfilter, "select"))==NULL)
selector="/";
/* Get config */
if (xmldb_get(h, "running", selector, 0, &xret) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>"
"<error-type>application</error-type>"
"<error-severity>error</error-severity>"
"<error-info>read-registry</error-info>"
"</rpc-error></rpc-reply>");
goto ok;
}
/* Get state data from plugins as defined by plugin_statedata(), if any */
assert(xret);
if (backend_statedata_call(h, selector, xret) < 0)
goto done;
cprintf(cbret, "<rpc-reply><data>");
/* if empty only <config/> */
if (xret!=NULL){
if (xml_child_nr(xret)){
if (xml_name_set(xret, "config") < 0)
goto done;
if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0)
goto done;
}
}
cprintf(cbret, "</data></rpc-reply>");
ok:
retval = 0;
done:
if (xret)
xml_free(xret);
return retval;
}
/*! Internal message: edit-config
*
* @param[in] h Clicon handle
@ -271,11 +334,26 @@ from_client_edit_config(clicon_handle h,
cxobj *x;
enum operation_type operation = OP_MERGE;
int piddb;
int non_config = 0;
yang_spec *yspec;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
if ((target = netconf_db_find(xn, "target")) == NULL){
clicon_err(OE_XML, 0, "db not found");
goto done;
}
if (xmldb_validate_db(target) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>invalid-value</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>No such database: %s</error-message>"
"</rpc-error></rpc-reply>", target);
goto ok;
}
/* Check if target locked by other client */
piddb = xmldb_islocked(h, target);
if (piddb && mypid != piddb){
@ -300,6 +378,20 @@ from_client_edit_config(clicon_handle h,
}
}
if ((xc = xpath_first(xn, "config")) != NULL){
if (xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
if (xml_apply(xc, CX_ELMNT, xml_non_config_data, &non_config) < 0)
goto done;
if (non_config){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>invalid-value</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>state data not allowed</error-message>"
"</rpc-error></rpc-reply>");
goto ok;
}
if (xmldb_put(h, target, operation, xc) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>"
@ -356,6 +448,16 @@ from_client_lock(clicon_handle h,
"</rpc-error></rpc-reply>");
goto ok;
}
if (xmldb_validate_db(db) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>invalid-value</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>No such database: %s</error-message>"
"</rpc-error></rpc-reply>", db);
goto ok;
}
/*
* A lock MUST not be granted if either of the following conditions is true:
* 1) A lock is already held by any NETCONF session or another entity.
@ -410,6 +512,15 @@ from_client_unlock(clicon_handle h,
"</rpc-error></rpc-reply>");
goto ok;
}
if (xmldb_validate_db(db) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>invalid-value</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>No such database: %s</error-message>"
"</rpc-error></rpc-reply>", db);
goto ok;
}
piddb = xmldb_islocked(h, db);
/*
* An unlock operation will not succeed if any of the following
@ -534,6 +645,16 @@ from_client_copy_config(clicon_handle h,
"</rpc-error></rpc-reply>");
goto ok;
}
if (xmldb_validate_db(source) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>invalid-value</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>No such database: %s</error-message>"
"</rpc-error></rpc-reply>", source);
goto ok;
}
if ((target = netconf_db_find(xe, "target")) == NULL){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>missing-element</error-tag>"
@ -543,6 +664,15 @@ from_client_copy_config(clicon_handle h,
"</rpc-error></rpc-reply>");
goto ok;
}
if (xmldb_validate_db(target) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>invalid-value</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>No such database: %s</error-message>"
"</rpc-error></rpc-reply>", target);
goto ok;
}
/* Check if target locked by other client */
piddb = xmldb_islocked(h, target);
if (piddb && mypid != piddb){
@ -556,7 +686,6 @@ from_client_copy_config(clicon_handle h,
piddb);
goto ok;
}
if (xmldb_copy(h, source, target) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>"
@ -601,6 +730,16 @@ from_client_delete_config(clicon_handle h,
"</rpc-error></rpc-reply>");
goto ok;
}
if (xmldb_validate_db(target) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>invalid-value</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>No such database: %s</error-message>"
"</rpc-error></rpc-reply>", target);
goto ok;
}
/* Check if target locked by other client */
piddb = xmldb_islocked(h, target);
if (piddb && mypid != piddb){
@ -808,6 +947,10 @@ from_client_msg(clicon_handle h,
if (from_client_unlock(h, xe, pid, cbret) < 0)
goto done;
}
else if (strcmp(name, "get") == 0){
if (from_client_get(h, xe, cbret) < 0)
goto done;
}
else if (strcmp(name, "close-session") == 0){
xmldb_unlock_all(h, pid);
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
@ -846,7 +989,7 @@ from_client_msg(clicon_handle h,
goto done;
}
else{
if ((ret = backend_netconf_plugin_callbacks(h, xe, ce, cbret)) < 0)
if ((ret = backend_rpc_cb_call(h, xe, ce, cbret)) < 0)
goto done;
if (ret == 0) /* not handled by callback */
cprintf(cbret, "<rpc-reply><rpc-error>"

View file

@ -146,9 +146,9 @@ validate_common(clicon_handle h,
goto done;
}
/* 2. Parse xml trees */
if (xmldb_get(h, "running", "/", &td->td_src, NULL, NULL) < 0)
if (xmldb_get(h, "running", "/", 1, &td->td_src) < 0)
goto done;
if (xmldb_get(h, candidate, "/", &td->td_target, NULL, NULL) < 0)
if (xmldb_get(h, candidate, "/", 1, &td->td_target) < 0)
goto done;
/* 3. Compute differences */
@ -212,7 +212,8 @@ validate_common(clicon_handle h,
* The code reverts changes if the commit fails. But if the revert
* fails, we just ignore the errors and proceed. Maybe we should
* do something more drastic?
* @param[in] h Clicon handle
* @param[in] h Clicon handle
* @param[in] candidate A candidate database, not necessarily "candidate"
*/
int
candidate_commit(clicon_handle h,
@ -283,17 +284,17 @@ from_client_commit(clicon_handle h,
piddb);
goto ok;
}
if (candidate_commit(h, "candidate") < 0){
clicon_debug(1, "Commit candidate failed");
/* XXX: candidate_validate should have proper error handling */
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>missing-attribute</error-tag>"
"<error-tag>invalid-value</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>%s</error-message>"
"</rpc-error></rpc-reply>",
clicon_err_reason);
goto ok;
goto ok;
}
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");

View file

@ -87,6 +87,8 @@ backend_terminate(clicon_handle h)
if ((yspec = clicon_dbspec_yang(h)) != NULL)
yspec_free(yspec);
plugin_finish(h);
/* Delete all backend plugin RPC callbacks */
backend_rpc_cb_delete_all();
if (pidfile)
unlink(pidfile);
if (sockpath)
@ -212,7 +214,7 @@ done:
static int
candb_reset(clicon_handle h)
{
int retval = -1;
int retval = -1;
if (xmldb_copy(h, "running", "tmp") < 0){
clicon_err(OE_UNIX, errno, "file copy");
@ -590,7 +592,7 @@ main(int argc, char **argv)
*(argv-1) = tmp;
if (reload_running){
/* This could be afailed validation, and we should not fail for that */
/* This could be a failed validation, and we should not fail for that */
(void)candidate_commit(h, "candidate");
}

View file

@ -68,11 +68,22 @@
* Types
*/
/* Following are specific to backend. For common see clicon_plugin.h
* @note the following should match the prototypes in clicon_backend.h
* @note the following should match the prototypes in clixon_backend.h
*/
#define PLUGIN_RESET "plugin_reset"
typedef int (plgreset_t)(clicon_handle h, char *dbname); /* Reset system status */
/*! Plugin callback, if defined called to get state data from plugin
* @param[in] h Clicon handle
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[in] xtop XML tree, <config/> on entry.
* @retval 0 OK
* @retval -1 Error
* @see xmldb_get
*/
#define PLUGIN_STATEDATA "plugin_statedata"
typedef int (plgstatedata_t)(clicon_handle h, char *xpath, cxobj *xtop);
#define PLUGIN_TRANS_BEGIN "transaction_begin"
#define PLUGIN_TRANS_VALIDATE "transaction_validate"
#define PLUGIN_TRANS_COMPLETE "transaction_complete"
@ -80,6 +91,7 @@ typedef int (plgreset_t)(clicon_handle h, char *dbname); /* Reset system status
#define PLUGIN_TRANS_END "transaction_end"
#define PLUGIN_TRANS_ABORT "transaction_abort"
typedef int (trans_cb_t)(clicon_handle h, transaction_data td); /* Transaction cbs */
/* Backend (config) plugins */
@ -90,12 +102,14 @@ struct plugin {
plgstart_t *p_start; /* Start */
plgexit_t *p_exit; /* Exit */
plgreset_t *p_reset; /* Reset state */
plgstatedata_t *p_statedata; /* State-data callback */
trans_cb_t *p_trans_begin; /* Transaction start */
trans_cb_t *p_trans_validate; /* Transaction validation */
trans_cb_t *p_trans_complete; /* Transaction validation complete */
trans_cb_t *p_trans_commit; /* Transaction commit */
trans_cb_t *p_trans_end; /* Transaction completed */
trans_cb_t *p_trans_abort; /* Transaction aborted */
};
/*
@ -212,6 +226,8 @@ backend_plugin_load (clicon_handle h,
clicon_debug(2, "%s callback registered.", PLUGIN_EXIT);
if ((new->p_reset = dlsym(handle, PLUGIN_RESET)) != NULL)
clicon_debug(2, "%s callback registered.", PLUGIN_RESET);
if ((new->p_statedata = dlsym(handle, PLUGIN_STATEDATA)) != NULL)
clicon_debug(2, "%s callback registered.", PLUGIN_STATEDATA);
if ((new->p_trans_begin = dlsym(handle, PLUGIN_TRANS_BEGIN)) != NULL)
clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_BEGIN);
if ((new->p_trans_validate = dlsym(handle, PLUGIN_TRANS_VALIDATE)) != NULL)
@ -700,3 +716,82 @@ plugin_transaction_abort(clicon_handle h,
return retval;
}
/*----------------------------------------------------------------------
* Backend state data callbacks
*/
/*! Go through all backend statedata callbacks and collect state data
* This is internal system call, plugin is invoked (does not call) this function
* Backend plugins can register
* @param[in] h clicon handle
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[in,out] xml XML tree.
* @retval -1 Error
* @retval 0 OK
*/
int
backend_statedata_call(clicon_handle h,
char *xpath,
cxobj *xtop)
{
int retval = -1;
struct plugin *p;
int i;
cxobj *x = NULL;
yang_spec *yspec;
cxobj **xvec = NULL;
size_t xlen;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
if (xtop==NULL){
clicon_err(OE_CFG, ENOENT, "XML tree expected");
goto done;
}
for (i = 0; i < nplugins; i++) {
p = &plugins[i];
if (p->p_statedata) {
if ((x = xml_new("config", NULL)) == NULL)
goto done;
if ((p->p_statedata)(h, xpath, x) < 0)
goto done;
if (xml_merge(xtop, x, yspec) < 0)
goto done;
if (x){
xml_free(x);
x = NULL;
}
}
}
{
/* Code complex to filter out anything that is outside of xpath */
if (xpath_vec(xtop, xpath?xpath:"/", &xvec, &xlen) < 0)
goto done;
/* If vectors are specified then mark the nodes found and
* then filter out everything else,
* otherwise return complete tree.
*/
if (xvec != NULL){
for (i=0; i<xlen; i++)
xml_flag_set(xvec[i], XML_FLAG_MARK);
}
/* Remove everything that is not marked */
if (!xml_flag(xtop, XML_FLAG_MARK))
if (xml_tree_prune_flagged_sub(xtop, XML_FLAG_MARK, 1, NULL) < 0)
goto done;
/* reset flag */
if (xml_apply(xtop, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
goto done;
}
retval = 0;
done:
if (x)
xml_free(x);
if (xvec)
free(xvec);
return retval;
}

View file

@ -69,6 +69,8 @@ int plugin_finish(clicon_handle h);
int plugin_reset_state(clicon_handle h, char *dbname);
int plugin_start_hooks(clicon_handle h, int argc, char **argv);
int backend_statedata_call(clicon_handle h, char *xpath, cxobj *xml);
transaction_data_t * transaction_new(void);
int transaction_free(transaction_data_t *);

View file

@ -72,6 +72,11 @@ int plugin_exit(clicon_handle h);
*/
int plugin_reset(clicon_handle h, char *dbname);
/*! Retreive statedata, add statedata to XML tree
* @see plgstatedata_ t
*/
int plugin_statedata(clicon_handle h, char *xpath, cxobj *xtop);
/*! Called before a commit/validate sequence begins. Eg setup state before commit
* @see trans_cb_t
*/

View file

@ -430,48 +430,57 @@ subscription_each(clicon_handle h,
return hs;
}
/* Database dependency description */
struct backend_netconf_reg {
qelem_t nr_qelem; /* List header */
backend_netconf_cb_t nr_callback; /* Validation/Commit Callback */
void *nr_arg; /* Application specific argument to cb */
char *nr_tag; /* Xml tag when matched, callback called */
};
typedef struct backend_netconf_reg backend_netconf_reg_t;
/*--------------------------------------------------------------------
* Backend netconf rpc callbacks
*/
typedef struct {
qelem_t rc_qelem; /* List header */
backend_rpc_cb rc_callback; /* RPC Callback */
void *rc_arg; /* Application specific argument to cb */
char *rc_tag; /* Xml tag when matched, callback called */
} backend_rpc_cb_entry;
static backend_netconf_reg_t *deps = NULL;
/*! Register netconf callback
/* List of backend rpc callback entries */
static backend_rpc_cb_entry *rpc_cb_list = NULL;
/*! Register netconf backend rpc callback
* Called from plugin to register a callback for a specific netconf XML tag.
*
* @param[in] h clicon handle
* @param[in] cb, Callback called
* @param[in] arg, Arg to send to callback
* @param[in] tag Xml tag when callback is made
* @see backend_rpc_cb_call
*/
int
backend_netconf_register_callback(clicon_handle h,
backend_netconf_cb_t cb, /* Callback called */
void *arg, /* Arg to send to callback */
char *tag) /* Xml tag when callback is made */
backend_rpc_cb_register(clicon_handle h,
backend_rpc_cb cb,
void *arg,
char *tag)
{
backend_netconf_reg_t *nr;
backend_rpc_cb_entry *rc;
if ((nr = malloc(sizeof(backend_netconf_reg_t))) == NULL) {
if ((rc = malloc(sizeof(backend_rpc_cb_entry))) == NULL) {
clicon_err(OE_DB, errno, "malloc: %s", strerror(errno));
goto catch;
}
memset (nr, 0, sizeof (*nr));
nr->nr_callback = cb;
nr->nr_arg = arg;
nr->nr_tag = strdup(tag); /* XXX strdup memleak */
INSQ(nr, deps);
memset (rc, 0, sizeof (*rc));
rc->rc_callback = cb;
rc->rc_arg = arg;
rc->rc_tag = strdup(tag); /* XXX strdup memleak */
INSQ(rc, rpc_cb_list);
return 0;
catch:
if (nr){
if (nr->nr_tag)
free(nr->nr_tag);
free(nr);
if (rc){
if (rc->rc_tag)
free(rc->rc_tag);
free(rc);
}
return -1;
}
/*! See if there is any callback registered for this tag
*
/*! Search netconf backend callbacks and invoke if match
* This is internal system call, plugin is invoked (does not call) this functino
* @param[in] h clicon handle
* @param[in] xe Sub-tree (under xorig) at child of rpc: <rpc><xn></rpc>.
* @param[in] ce Client (session) entry
@ -480,28 +489,49 @@ catch:
* @retval -1 Error
* @retval 0 OK, not found handler.
* @retval 1 OK, handler called
* @see backend_rpc_cb_register
*/
int
backend_netconf_plugin_callbacks(clicon_handle h,
cxobj *xe,
struct client_entry *ce,
cbuf *cbret)
backend_rpc_cb_call(clicon_handle h,
cxobj *xe,
struct client_entry *ce,
cbuf *cbret)
{
backend_netconf_reg_t *nreg;
int retval;
backend_rpc_cb_entry *rc;
int retval = -1;
if (deps == NULL)
if (rpc_cb_list == NULL)
return 0;
nreg = deps;
rc = rpc_cb_list;
do {
if (strcmp(nreg->nr_tag, xml_name(xe)) == 0){
if ((retval = nreg->nr_callback(h, xe, ce, cbret, nreg->nr_arg)) < 0)
return -1;
else
return 1; /* handled */
if (strcmp(rc->rc_tag, xml_name(xe)) == 0){
if ((retval = rc->rc_callback(h, xe, ce, cbret, rc->rc_arg)) < 0)
goto done;
else{
retval = 1; /* handled */
goto done;
}
}
nreg = NEXTQ(backend_netconf_reg_t *, nreg);
} while (nreg != deps);
rc = NEXTQ(backend_rpc_cb_entry *, rc);
} while (rc != rpc_cb_list);
retval = 0;
done:
return retval;
}
/*! Delete all state data callbacks.
*/
int
backend_rpc_cb_delete_all(void)
{
backend_rpc_cb_entry *rc;
while((rc = rpc_cb_list) != NULL) {
DELQ(rc, rpc_cb_list, backend_rpc_cb_entry *);
if (rc->rc_tag)
free(rc->rc_tag);
free(rc);
}
return 0;
}

View file

@ -44,13 +44,15 @@
* Types
*/
struct client_entry;
typedef int (*backend_netconf_cb_t)(
typedef int (*backend_rpc_cb)(
clicon_handle h,
cxobj *xe, /* Request: <rpc><xn></rpc> */
struct client_entry *ce, /* Client session */
cbuf *cbret, /* Reply eg <rpc-reply>... */
void *arg /* Argument given at register */
cxobj *xe, /* Request: <rpc><xn></rpc> */
struct client_entry *ce, /* Client session */
cbuf *cbret,/* Reply eg <rpc-reply>... */
void *arg /* Argument given at register */
);
typedef backend_rpc_cb backend_netconf_cb_t; /* XXX backward compat */
/*! Generic downcall registration.
* Enables any function to be called from (cli) frontend
@ -88,14 +90,16 @@ int subscription_delete(clicon_handle h, char *stream,
subscription_fn_t fn, void *arg);
struct handle_subscription *subscription_each(clicon_handle h,
struct handle_subscription *hprev);
struct handle_subscription *hprev);
int backend_netconf_register_callback(clicon_handle h,
backend_netconf_cb_t cb, /* Callback called */
void *arg, /* Arg to send to callback */
char *tag); /* Xml tag when callback is made */
/* XXX backward compat */
#define backend_netconf_register_callback(a,b,c,d) backend_rpc_cb_register(a,b,c,d)
int backend_rpc_cb_register(clicon_handle h, backend_rpc_cb cb, void *arg,
char *tag);
int backend_netconf_plugin_callbacks(clicon_handle h, cxobj *xe,
struct client_entry *ce, cbuf *cbret);
int backend_rpc_cb_call(clicon_handle h, cxobj *xe, struct client_entry *ce,
cbuf *cbret);
int backend_rpc_cb_delete_all(void);
#endif /* _CLIXON_BACKEND_HANDLE_H_ */

View file

@ -243,7 +243,7 @@ cli_dbxml(clicon_handle h,
xml_type_set(xa, CX_ATTR);
if (xml_value_set(xa, xml_operation2str(op)) < 0)
goto done;
if (y->yn_keyword != Y_LIST){
if (y->yn_keyword != Y_LIST && y->yn_keyword != Y_LEAF_LIST){
len = cvec_len(cvv);
if (len > 1){
cval = cvec_i(cvv, len-1);
@ -644,6 +644,7 @@ compare_dbs(clicon_handle h,
{
cxobj *xc1 = NULL; /* running xml */
cxobj *xc2 = NULL; /* candidate xml */
cxobj *xerr;
int retval = -1;
int astext;
@ -657,8 +658,16 @@ compare_dbs(clicon_handle h,
astext = 0;
if (clicon_rpc_get_config(h, "running", "/", &xc1) < 0)
goto done;
if ((xerr = xpath_first(xc1, "/rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
if (clicon_rpc_get_config(h, "candidate", "/", &xc2) < 0)
goto done;
if ((xerr = xpath_first(xc2, "/rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
if (compare_xmls(xc1, xc2, astext) < 0) /* astext? */
goto done;
retval = 0;
@ -800,6 +809,7 @@ save_config_file(clicon_handle h,
char *dbstr;
char *varstr;
cxobj *xt = NULL;
cxobj *xerr;
FILE *f = NULL;
if (cvec_len(argv) != 2){
@ -825,6 +835,10 @@ save_config_file(clicon_handle h,
filename = cv_string_get(cv);
if (clicon_rpc_get_config(h, dbstr,"/", &xt) < 0)
goto done;
if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
if ((f = fopen(filename, "wb")) == NULL){
clicon_err(OE_CFG, errno, "Creating file %s", filename);
goto done;
@ -1122,6 +1136,7 @@ cli_copy_config(clicon_handle h,
char *tovar;
cg_var *tocv;
char *toname;
cxobj *xerr;
if (cvec_len(argv) != 5){
clicon_err(OE_PLUGIN, 0, "%s: Requires four elements: <db> <xpath> <keyname> <from> <to>", __FUNCTION__);
@ -1164,6 +1179,10 @@ cli_copy_config(clicon_handle h,
/* Get from object configuration and store in x1 */
if (clicon_rpc_get_config(h, db, cbuf_get(cb), &x1) < 0)
goto done;
if ((xerr = xpath_first(x1, "/rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
/* Get to variable -> cv -> to name */
if ((tocv = cvec_find_var(cvv, tovar)) == NULL){

View file

@ -657,7 +657,7 @@ clicon_parse(clicon_handle h,
}
res = cliread_parse(cli_cligen(h), cmd, pt, &match_obj, cvv);
if (res != CG_MATCH)
pt_expand_cleanup_1(pt);
pt_expand_cleanup_1(pt); /* XXX change to pt_expand_treeref_cleanup */
if (msav){
cli_tree_active_set(h, msav);
free(msav);
@ -689,7 +689,7 @@ clicon_parse(clicon_handle h,
}
if ((r = clicon_eval(h, cmd, match_obj, cvv)) < 0)
cli_handler_err(stdout);
pt_expand_cleanup_1(pt);
pt_expand_cleanup_1(pt); /* XXX change to pt_expand_treeref_cleanup */
if (result)
*result = r;
goto done;

View file

@ -104,6 +104,7 @@ expand_dbvar(void *h,
cxobj *xt = NULL;
char *xpath = NULL;
cxobj **xvec = NULL;
cxobj *xerr;
size_t xlen = 0;
cxobj *x;
char *bodystr;
@ -142,6 +143,10 @@ expand_dbvar(void *h,
/* XXX read whole configuration, why not send xpath? */
if (clicon_rpc_get_config(h, dbstr, "/", &xt) < 0)
goto done;
if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
/* One round to detect duplicates
* XXX The code below would benefit from some cleanup
*/
@ -379,6 +384,7 @@ cli_show_config(clicon_handle h,
char *val = NULL;
cxobj *xt = NULL;
cxobj *xc;
cxobj *xerr;
enum genmodel_type gt;
if (cvec_len(argv) != 3 && cvec_len(argv) != 4){
@ -428,6 +434,10 @@ cli_show_config(clicon_handle h,
/* Get configuration from database */
if (clicon_rpc_get_config(h, db, cbuf_get(cbxpath), &xt) < 0)
goto done;
if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
/* Print configuration according to format */
switch (format){
case FORMAT_XML:
@ -487,6 +497,7 @@ show_conf_xpath(clicon_handle h,
char *xpath;
cg_var *cv;
cxobj *xt = NULL;
cxobj *xerr;
cxobj **xv = NULL;
size_t xlen;
int i;
@ -507,6 +518,10 @@ show_conf_xpath(clicon_handle h,
xpath = cv_string_get(cv);
if (clicon_rpc_get_config(h, str, xpath, &xt) < 0)
goto done;
if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
if (xpath_vec(xt, xpath, &xv, &xlen) < 0)
goto done;
for (i=0; i<xlen; i++)

View file

@ -530,6 +530,76 @@ netconf_unlock(clicon_handle h,
return netconf_lock(h, xn, xret);
}
/*! Get running configuration and device state information
*
*
* @param[in] h Clicon handle
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
* @param[out] xret Return XML, error or OK
* @note filter type subtree and xpath is supported, but xpath is preferred, and
* better performance and tested. Please use xpath.
*
* @example
* <rpc><get><filter type="xpath" select="//SenderTwampIpv4"/>
* </get></rpc>]]>]]>
*/
static int
netconf_get(clicon_handle h,
cxobj *xn,
cxobj **xret)
{
cxobj *xfilter; /* filter */
int retval = -1;
char *ftype = NULL;
cxobj *xfilterconf;
cxobj *xconf;
/* ie <filter>...</filter> */
if ((xfilter = xpath_first(xn, "filter")) != NULL)
ftype = xml_find_value(xfilter, "type");
if (ftype == NULL || strcmp(ftype, "xpath")==0){
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
}
else if (strcmp(ftype, "subtree")==0){
/* Default rfc filter is subtree. I prefer xpath and use it internally.
Get whole subtree and then filter aftwerwards. This is suboptimal.
Therefore please use xpath.
*/
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
if (xfilter &&
(xfilterconf = xpath_first(xfilter, "//configuration"))!= NULL &&
(xconf = xpath_first(*xret, "/rpc-reply/data/configuration")) != NULL){
/* xml_filter removes parts of xml tree not matching */
if ((strcmp(xml_name(xfilterconf), xml_name(xconf))!=0) ||
xml_filter(xfilterconf, xconf) < 0){
clicon_xml_parse(xret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>"
"<error-type>applicatio</error-type>"
"<error-severity>error</error-severity>"
"<error-info>filtering</error-info>"
"</rpc-error></rpc-reply>");
}
}
}
else{
clicon_xml_parse(xret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>"
"<error-type>applicatio</error-type>"
"<error-severity>error</error-severity>"
"<error-message>filter type not supported</error-message>"
"<error-info>type</error-info>"
"</rpc-error></rpc-reply>");
}
// ok: /* netconf error is not fatal */
retval = 0;
done:
return retval;
}
/*! Close a (user) session
<close-session/>
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
@ -818,6 +888,8 @@ netconf_rpc_dispatch(clicon_handle h,
return netconf_lock(h, xe, xret);
else if (strcmp(xml_name(xe), "unlock") == 0)
return netconf_unlock(h, xe, xret);
else if (strcmp(xml_name(xe), "get") == 0)
return netconf_get(h, xe, xret);
else if (strcmp(xml_name(xe), "close-session") == 0)
return netconf_close_session(h, xe, xret);
else if (strcmp(xml_name(xe), "kill-session") == 0)

View file

@ -60,7 +60,7 @@ olof@vandal> curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=et
}
]
curl -sX POST -d '{"clicon":{"interfaces":{"interface":{"name":"eth1","type":"eth","enabled":"true"}}}}' http://localhost/restconf/data
curl -sX POST -d '{"interfaces":{"interface":{"name":"eth1","type":"eth","enabled":"true"}}}' http://localhost/restconf/data
```
### Debugging

View file

@ -57,6 +57,96 @@
#include "restconf_lib.h"
/* See RFC 8040 Section 7: Mapping from NETCONF<error-tag> to Status Code
* and RFC 6241 Appendix A. NETCONF Error list
*/
static const map_str2int netconf_restconf_map[] = {
{"in-use", 409},
{"invalid-value", 400},
{"invalid-value", 404},
{"invalid-value", 406},
{"too-big", 413}, /* request */
{"too-big", 400}, /* response */
{"missing-attribute", 400},
{"bad-attribute", 400},
{"unknown-attribute", 400},
{"bad-element", 400},
{"unknown-element", 400},
{"unknown-namespace", 400},
{"access-denied", 401},
{"access-denied", 403},
{"lock-denied", 409},
{"resource-denied", 409},
{"rollback-failed", 500},
{"data-exists", 409},
{"data-missing", 409},
{"operation-not-supported",405},
{"operation-not-supported",501},
{"operation-failed", 412},
{"operation-failed", 500},
{"partial-operation", 500},
{"malformed-message", 400},
{NULL, -1}
};
/* See 7231 Section 6.1
*/
static const map_str2int http_reason_phrase_map[] = {
{"Continue", 100},
{"Switching Protocols", 101},
{"OK", 200},
{"Created", 201},
{"Accepted", 202},
{"Non-Authoritative Information", 203},
{"No Content", 204},
{"Reset Content", 205},
{"Partial Content", 206},
{"Multiple Choices", 300},
{"Moved Permanently", 301},
{"Found", 302},
{"See Other", 303},
{"Not Modified", 304},
{"Use Proxy", 305},
{"Temporary Redirect", 307},
{"Bad Request", 400},
{"Unauthorized", 401},
{"Payment Required", 402},
{"Forbidden", 403},
{"Not Found", 404},
{"Method Not Allowed", 405},
{"Not Acceptable", 406},
{"Proxy Authentication Required", 407},
{"Request Timeout", 408},
{"Conflict", 409},
{"Gone", 410},
{"Length Required", 411},
{"Precondition Failed", 412},
{"Payload Too Large", 413},
{"URI Too Long", 414},
{"Unsupported Media Type", 415},
{"Range Not Satisfiable", 416},
{"Expectation Failed", 417},
{"Upgrade Required", 426},
{"Internal Server Error", 500},
{"Not Implemented", 501},
{"Bad Gateway", 502},
{"Service Unavailable", 503},
{"Gateway Timeout", 504},
{"HTTP Version Not Supported", 505},
{NULL, -1}
};
int
restconf_err2code(char *tag)
{
return clicon_str2int(netconf_restconf_map, tag);
}
const char *
restconf_code2reason(int code)
{
return clicon_int2str(http_reason_phrase_map, code);
}
/*!
*/
@ -90,6 +180,16 @@ badrequest(FCGX_Request *r)
return 0;
}
int
notimplemented(FCGX_Request *r)
{
clicon_debug(1, "%s", __FUNCTION__);
FCGX_FPrintF(r->out, "Status: 501\r\n");
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "<h1>Not Implemented/h1>\n");
return 0;
}
int
conflict(FCGX_Request *r)
{

View file

@ -43,8 +43,11 @@
/*
* Prototypes
*/
int restconf_err2code(char *tag);
const char *restconf_code2reason(int code);
int notfound(FCGX_Request *r);
int badrequest(FCGX_Request *r);
int notimplemented(FCGX_Request *r);
int conflict(FCGX_Request *r);
int clicon_debug_xml(int dbglevel, char *str, cxobj *cx);
int test(FCGX_Request *r, int dbg);

View file

@ -156,6 +156,11 @@ api_data_get_gen(clicon_handle h,
cxobj **vec = NULL;
yang_spec *yspec;
cxobj *xret = NULL;
cxobj *xerr;
cxobj *xtag;
cbuf *cbj = NULL;;
int code;
const char *reason_phrase;
clicon_debug(1, "%s", __FUNCTION__);
yspec = clicon_dbspec_yang(h);
@ -166,16 +171,45 @@ api_data_get_gen(clicon_handle h,
goto done;
}
clicon_debug(1, "%s path:%s", __FUNCTION__, cbuf_get(path));
if (clicon_rpc_get_config(h, "running", cbuf_get(path), &xret) < 0){
if (clicon_rpc_get(h, cbuf_get(path), &xret) < 0){
notfound(r);
goto done;
}
#if 0 /* DEBUG */
{
cbuf *cb = cbuf_new();
clicon_xml2cbuf(cb, xret, 0, 0);
xml2json_cbuf(cb, xret, 1);
clicon_debug(1, "%s xret:%s", __FUNCTION__, cbuf_get(cb));
cbuf_free(cb);
}
#endif
if ((xerr = xpath_first(xret, "/rpc-error")) != NULL){
if ((cbj = cbuf_new()) == NULL)
goto done;
if ((xtag = xpath_first(xerr, "/error-tag")) == NULL){
notfound(r); /* bad reply? */
goto done;
}
code = restconf_err2code(xml_body(xtag));
if ((reason_phrase = restconf_code2reason(code)) == NULL)
reason_phrase="";
clicon_debug(1, "%s code:%d reason phrase:%s",
__FUNCTION__, code, reason_phrase);
if (xml_name_set(xerr, "error") < 0)
goto done;
if (xml2json_cbuf(cbj, xerr, 1) < 0)
goto done;
FCGX_FPrintF(r->out, "Status: %d %s\r\n", code, reason_phrase);
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+json\r\n\r\n");
FCGX_FPrintF(r->out, "\r\n");
FCGX_FPrintF(r->out, "{\r\n");
FCGX_FPrintF(r->out, " \"ietf-restconf:errors\" : {\r\n");
FCGX_FPrintF(r->out, " %s", cbuf_get(cbj));
FCGX_FPrintF(r->out, " }\r\n");
FCGX_FPrintF(r->out, "}\r\n");
goto ok;
}
if ((cbx = cbuf_new()) == NULL)
goto done;
FCGX_SetExitStatus(200, r->out); /* OK */
@ -197,6 +231,8 @@ api_data_get_gen(clicon_handle h,
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cbx)
cbuf_free(cbx);
if (cbj)
cbuf_free(cbj);
if (path)
cbuf_free(path);
if (xret)
@ -505,8 +541,7 @@ api_data_patch(clicon_handle h,
cvec *qvec,
char *data)
{
badrequest(r);
// return api_data_edit(h, r, api_path, pcvec, pi, qvec, data, OP_MERGE);
notimplemented(r);
return 0;
}
@ -555,6 +590,7 @@ api_data_delete(clicon_handle h,
goto done;
if ((cbx = cbuf_new()) == NULL)
goto done;
if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0)
goto done;
if (clicon_rpc_edit_config(h, "candidate",

View file

@ -95,8 +95,6 @@ CLICON_BACKEND_PIDFILE localstatedir/APPNAME/APPNAME.pidfile
# Set if all configuration changes are committed directly, commit command unnecessary
# CLICON_AUTOCOMMIT 0
# CLICON_COMMIT_ORDER 0
# Name of master plugin (both frontend and backend). Master plugin has special
# callbacks for frontends. See clicon user manual for more info.
# CLICON_MASTER_PLUGIN master

6
configure vendored
View file

@ -2136,7 +2136,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
CLIXON_VERSION_MAJOR="3"
CLIXON_VERSION_MINOR="3"
CLIXON_VERSION_PATCH="1"
CLIXON_VERSION_PATCH="2"
CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\""
# Fix to specific version (eg 3.5) or head (3)
CLIGEN_VERSION="3"
@ -2172,8 +2172,8 @@ _ACEOF
# Bind to specific CLIgen version
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: CLIXON version is ${CLIXON_VERSION}" >&5
$as_echo "CLIXON version is ${CLIXON_VERSION}" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: CLIXON version is ${CLIXON_VERSION}_PRE" >&5
$as_echo "CLIXON version is ${CLIXON_VERSION}_PRE" >&6; }
ac_aux_dir=
for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do

View file

@ -43,7 +43,7 @@ AC_INIT(lib/clixon/clixon.h.in)
CLIXON_VERSION_MAJOR="3"
CLIXON_VERSION_MINOR="3"
CLIXON_VERSION_PATCH="1"
CLIXON_VERSION_PATCH="2"
CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\""
# Fix to specific version (eg 3.5) or head (3)
CLIGEN_VERSION="3"
@ -62,7 +62,7 @@ AC_SUBST(CLIXON_VERSION_MAJOR)
AC_SUBST(CLIXON_VERSION_MINOR)
AC_SUBST(CLIGEN_VERSION) # Bind to specific CLIgen version
AC_MSG_RESULT(CLIXON version is ${CLIXON_VERSION})
AC_MSG_RESULT(CLIXON version is ${CLIXON_VERSION}_PRE)
AC_CANONICAL_TARGET
AC_SUBST(CC)

View file

@ -213,7 +213,7 @@ main(int argc, char **argv)
if (strcmp(cmd, "get")==0){
if (argc != 1 && argc != 2)
usage(argv0);
if (xmldb_get(h, db, argc==2?argv[1]:"/", &xt, NULL, 0) < 0)
if (xmldb_get(h, db, argc==2?argv[1]:"/", 0, &xt) < 0)
goto done;
clicon_xml2file(stdout, xt, 0, 0);

View file

@ -71,17 +71,6 @@
* cli_expand_var_generate | yang2api_path_fmt |
* yang -------------> | |
* +----------------+
* xmldb_get_tree
* - compare_dbs
* - netconf
* - validate
* - from_client_save
*
* xmldb_get_vec
* - restconf
* - expand_dbvar
* - show_conf_xpath
*
* dependency on clixon handle:
* clixon_xmldb_dir()
* clicon_dbspec_yang(h)
@ -571,38 +560,15 @@ kv_setopt(xmldb_handle xh,
/*! Get content of database using xpath. return a set of matching sub-trees
* The function returns a minimal tree that includes all sub-trees that match
* xpath.
* @param[in] dbname Name of database to search in (filename including dir path
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[out] xtop Single XML tree which xvec points to. Free with xml_free()
* @param[out] xvec Vector of xml trees. Free after use.
* @param[out] xlen Length of vector.
* @retval 0 OK
* @retval -1 Error
* @code
* cxobj *xt;
* cxobj **xvec;
* size_t xlen;
* if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]",
* &xt, &xvec, &xlen) < 0)
* err;
* for (i=0; i<xlen; i++){
* xn = xv[i];
* ...
* }
* xml_free(xt);
* free(xvec);
* @endcode
* @note if xvec is given, then purge tree, if not return whole tree.
* @see xpath_vec
* This is a clixon datastore plugin of the the xmldb api
* @see xmldb_get
*/
int
kv_get(xmldb_handle xh,
char *db,
char *xpath,
cxobj **xtop,
cxobj ***xvec0,
size_t *xlen0)
int config,
cxobj **xtop)
{
int retval = -1;
struct kv_handle *kh = handle(xh);
@ -651,19 +617,14 @@ kv_get(xmldb_handle xh,
}
/* Top is special case */
if (!xml_flag(xt, XML_FLAG_MARK))
if (xml_tree_prune_flagged(xt, XML_FLAG_MARK, 1, NULL) < 0)
if (xml_tree_prune_flagged_sub(xt, XML_FLAG_MARK, 1, NULL) < 0)
goto done;
if (xml_apply(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
goto done;
if (xvec0 && xlen0){
*xvec0 = xvec;
xvec = NULL;
*xlen0 = xlen;
xlen = 0;
}
/* Add default values (if not set) */
if (xml_apply(xt, CX_ELMNT, xml_default, NULL) < 0)
goto done;
/* XXX does not work for top-level */
/* Order XML children according to YANG */
if (xml_apply(xt, CX_ELMNT, xml_order, NULL) < 0)
goto done;
if (xml_apply(xt, CX_ELMNT, xml_sanity, NULL) < 0)
@ -816,33 +777,15 @@ put(char *dbfile,
return retval;
}
/*! Modify database provided an xml tree and an operation
*
* @param[in] xh XMLDB handle
* @param[in] db running or candidate
* @param[in] xt xml-tree. Top-level symbol is dummy
* @param[in] op OP_MERGE: just add it.
* OP_REPLACE: first delete whole database
* OP_NONE: operation attribute in xml determines operation
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [restconf-draft 13])
* @retval 0 OK
* @retval -1 Error
* The xml may contain the "operation" attribute which defines the operation.
* @code
* cxobj *xt;
* if (clicon_xml_parse_str("<a>17</a>", &xt) < 0)
* err;
* if (xmldb_put(h, "running", OP_MERGE, NULL, xt) < 0)
* err;
* @endcode
* @see xmldb_put_xkey for single key
* This is a clixon datastore plugin of the the xmldb api
* @see xmldb_put
*/
int
kv_put(xmldb_handle xh,
char *db,
enum operation_type op,
cxobj *xt)
cxobj *xt)
{
int retval = -1;
struct kv_handle *kh = handle(xh);

View file

@ -39,8 +39,7 @@
/*
* Prototypes
*/
int kv_get(xmldb_handle h, char *db, char *xpath,
cxobj **xtop, cxobj ***xvec, size_t *xlen);
int kv_get(xmldb_handle h, char *db, char *xpath, int config, cxobj **xtop);
int kv_put(xmldb_handle h, char *db, enum operation_type op, cxobj *xt);
int kv_dump(FILE *f, char *dbfilename, char *rxkey);
int kv_copy(xmldb_handle h, char *from, char *to);

View file

@ -85,7 +85,7 @@ install: $(PLUGIN)
install-include:
uninstall:
rm -rf $(DESTDIR)$(clixon_LIBDIR)/xmldb/$(PLUGIN)
rm -rf $(DESTDIR)$(libdir)/xmldb/$(PLUGIN)
TAGS:
find . -name '*.[chyl]' -print | etags -

View file

@ -68,9 +68,10 @@
/*! Internal structure of text datastore handle.
*/
struct text_handle {
int th_magic; /* magic */
char *th_dbdir; /* Directory of database files */
yang_spec *th_yangspec; /* Yang spec if this datastore */
int th_magic; /* magic */
char *th_dbdir; /* Directory of database files */
yang_spec *th_yangspec; /* Yang spec if this datastore */
clicon_hash_t *th_dbs; /* Hash of databases */
};
/*! Check struct magic number for sanity checks
@ -85,15 +86,6 @@ text_handle_check(xmldb_handle xh)
return th->th_magic == TEXT_HANDLE_MAGIC ? 0 : -1;
}
/*! Database locking for candidate and running non-persistent
* Store an integer for running and candidate containing
* the session-id of the client holding the lock.
* @note This should probably be on file-system
*/
static int _running_locked = 0;
static int _candidate_locked = 0;
static int _startup_locked = 0;
/*! Translate from symbolic database name to actual filename in file-system
* @param[in] th text handle handle
* @param[in] db Symbolic database name, eg "candidate", "running"
@ -107,8 +99,8 @@ static int _startup_locked = 0;
*/
static int
text_db2file(struct text_handle *th,
char *db,
char **filename)
char *db,
char **filename)
{
int retval = -1;
cbuf *cb;
@ -122,13 +114,6 @@ text_db2file(struct text_handle *th,
clicon_err(OE_XML, errno, "dbdir not set");
goto done;
}
if (strcmp(db, "running") != 0 &&
strcmp(db, "candidate") != 0 &&
strcmp(db, "startup") != 0 &&
strcmp(db, "tmp") != 0){
clicon_err(OE_XML, 0, "No such database: %s", db);
goto done;
}
cprintf(cb, "%s/%s_db", dir, db);
if ((*filename = strdup4(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
@ -159,6 +144,8 @@ text_connect(void)
}
memset(th, 0, size);
th->th_magic = TEXT_HANDLE_MAGIC;
if ((th->th_dbs = hash_init()) == NULL)
goto done;
xh = (xmldb_handle)th;
done:
return xh;
@ -178,6 +165,8 @@ text_disconnect(xmldb_handle xh)
if (th){
if (th->th_dbdir)
free(th->th_dbdir);
if (th->th_dbs)
hash_free(th->th_dbs);
free(th);
}
retval = 0;
@ -245,37 +234,6 @@ text_setopt(xmldb_handle xh,
return retval;
}
/*! Populate with spec
* @param[in] xt XML tree with some node marked
*/
int
xml_spec_populate(cxobj *x,
void *arg)
{
int retval = -1;
yang_spec *yspec = (yang_spec*)arg;
char *name;
yang_stmt *y; /* yang node */
cxobj *xp; /* xml parent */
yang_stmt *yp; /* parent yang */
name = xml_name(x);
if ((xp = xml_parent(x)) != NULL &&
(yp = xml_spec(xp)) != NULL)
y = yang_find_syntax((yang_node*)yp, xml_name(x));
else
y = yang_find_topnode(yspec, name); /* still NULL for config */
if (y==NULL){
clicon_err(OE_XML, EBADF, "yang spec not found for xml node '%s' xml parent name: '%s' yangspec:'%s']",
name,
xp?xml_name(xp):"", yp?yp->ys_argument:"");
goto done;
}
xml_spec_set(x, y);
retval = 0;
done:
return retval;
}
/*! Ensure that xt only has a single sub-element and that is "config"
*/
@ -318,39 +276,15 @@ singleconfigroot(cxobj *xt,
/*! Get content of database using xpath. return a set of matching sub-trees
* The function returns a minimal tree that includes all sub-trees that match
* xpath.
* @param[in] xh XMLDB handle
* @param[in] dbname Name of database to search in (filename including dir path
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[out] xtop Single XML tree which xvec points to. Free with xml_free()
* @param[out] xvec Vector of xml trees. Free after use.
* @param[out] xlen Length of vector.
* @retval 0 OK
* @retval -1 Error
* @code
* cxobj *xt;
* cxobj **xvec;
* size_t xlen;
* if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]",
* &xt, &xvec, &xlen) < 0)
* err;
* for (i=0; i<xlen; i++){
* xn = xv[i];
* ...
* }
* xml_free(xt);
* free(xvec);
* @endcode
* @note if xvec is given, then purge tree, if not return whole tree.
* @see xpath_vec
* This is a clixon datastore plugin of the the xmldb api
* @see xmldb_get
*/
int
text_get(xmldb_handle xh,
char *db,
char *xpath,
cxobj **xtop,
cxobj ***xvec0,
size_t *xlen0)
int config,
cxobj **xtop)
{
int retval = -1;
char *dbfile = NULL;
@ -368,7 +302,7 @@ text_get(xmldb_handle xh,
clicon_err(OE_XML, 0, "dbfile NULL");
goto done;
}
if ((yspec = th->th_yangspec) == NULL){
if ((yspec = th->th_yangspec) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
@ -393,45 +327,44 @@ text_get(xmldb_handle xh,
goto done;
}
/* Here xt looks like: <config>...</config> */
/* Validate existing config tree */
/* Add yang specification backpointer to all XML nodes */
if (xml_apply(xt, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
/* XXX Maybe the below is general function and should be moved to xmldb? */
if (xpath_vec(xt, xpath?xpath:"/", &xvec, &xlen) < 0)
goto done;
/* If vectors are specified then filter out everything else,
/* If vectors are specified then mark the nodes found and
* then filter out everything else,
* otherwise return complete tree.
*/
if (xvec != NULL){
for (i=0; i<xlen; i++)
xml_flag_set(xvec[i], XML_FLAG_MARK);
}
/* Top is special case */
/* Remove everything that is not marked */
if (!xml_flag(xt, XML_FLAG_MARK))
if (xml_tree_prune_flagged(xt, XML_FLAG_MARK, 1, NULL) < 0)
if (xml_tree_prune_flagged_sub(xt, XML_FLAG_MARK, 1, NULL) < 0)
goto done;
/* reset flag */
if (xml_apply(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
goto done;
if (xml_apply(xt, CX_ELMNT, xml_spec_populate, yspec) < 0)
/* filter out state (operations) data if config not set. Mark all nodes
that are not config data */
if (config && xml_apply(xt, CX_ELMNT, xml_non_config_data, NULL) < 0)
goto done;
/* Remove (prune) nodes that are marked (that does not pass test) */
if (xml_tree_prune_flagged(xt, XML_FLAG_MARK, 1) < 0)
goto done;
/* Add default values (if not set) */
if (xml_apply(xt, CX_ELMNT, xml_default, NULL) < 0)
goto done;
/* XXX does not work for top-level */
/* Order XML children according to YANG */
if (xml_apply(xt, CX_ELMNT, xml_order, NULL) < 0)
goto done;
if (xml_apply(xt, CX_ELMNT, xml_sanity, NULL) < 0)
goto done;
if (debug>1)
clicon_xml2file(stderr, xt, 0, 1);
if (xvec0 && xlen0){
*xvec0 = xvec;
xvec = NULL;
*xlen0 = xlen;
xlen = 0;
}
*xtop = xt;
xt = NULL;
retval = 0;
@ -526,11 +459,10 @@ match_base_child(cxobj *x0,
/*! Modify a base tree x0 with x1 with yang spec y according to operation op
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
* @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL
* @param[in] x0p Parent of x0
* @param[in] x1 xml tree which modifies base
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
* @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL
* @param[in] yspec Top-level yang spec (if y is NULL)
* Assume x0 and x1 are same on entry and that y is the spec
* @see put in clixon_keyvalue.c
*/
@ -733,32 +665,15 @@ text_modify_top(cxobj *x0,
return retval;
}
/*! Modify database provided an xml tree and an operation
*
* @param[in] xh XMLDB handle
* @param[in] db running or candidate
* @param[in] op OP_MERGE: just add it.
* OP_REPLACE: first delete whole database
* OP_NONE: operation attribute in xml determines operation
* @param[in] x1 xml-tree to merge/replace. Top-level symbol is 'config'.
* Should be empty or '<config/>' if delete?
* @retval 0 OK
* @retval -1 Error
* The xml may contain the "operation" attribute which defines the operation.
* @code
* cxobj *xt;
* if (clicon_xml_parse_str("<a>17</a>", &xt) < 0)
* err;
* if (xmldb_put(h, "running", OP_MERGE, "/", xt) < 0)
* err;
* @endcode
y */
* This is a clixon datastore plugin of the the xmldb api
* @see xmldb_put
*/
int
text_put(xmldb_handle xh,
char *db,
enum operation_type op,
cxobj *x1)
cxobj *x1)
{
int retval = -1;
struct text_handle *th = handle(xh);
@ -799,7 +714,6 @@ text_put(xmldb_handle xh,
}
/* 2. File is not empty <top><config>...</config></top> -> replace root */
else{
/* There should only be one element and called config */
if (singleconfigroot(x0, &x0) < 0)
goto done;
@ -811,11 +725,11 @@ text_put(xmldb_handle xh,
goto done;
}
/* Validate existing config tree */
/* Add yang specification backpointer to all XML nodes */
if (xml_apply(x0, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
/* Validate modification tree */
/* Add yang specification backpointer to all XML nodes */
if (xml_apply(x1, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
@ -826,7 +740,7 @@ text_put(xmldb_handle xh,
goto done;
/* Remove NONE nodes if all subs recursively are also NONE */
if (xml_tree_prune_flagged(x0, XML_FLAG_NONE, 0, NULL) <0)
if (xml_tree_prune_flagged_sub(x0, XML_FLAG_NONE, 0, NULL) <0)
goto done;
if (xml_apply(x0, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
(void*)XML_FLAG_NONE) < 0)
@ -871,8 +785,8 @@ text_put(xmldb_handle xh,
*/
int
text_copy(xmldb_handle xh,
char *from,
char *to)
char *from,
char *to)
{
int retval = -1;
struct text_handle *th = handle(xh);
@ -896,7 +810,7 @@ text_copy(xmldb_handle xh,
}
/*! Lock database
* @param[in] xh XMLDB handle
* @param[in] xh XMLDB handle
* @param[in] db Database
* @param[in] pid Process id
* @retval -1 Error
@ -904,17 +818,12 @@ text_copy(xmldb_handle xh,
*/
int
text_lock(xmldb_handle xh,
char *db,
int pid)
char *db,
int pid)
{
// struct text_handle *th = handle(xh);
struct text_handle *th = handle(xh);
if (strcmp("running", db) == 0)
_running_locked = pid;
else if (strcmp("candidate", db) == 0)
_candidate_locked = pid;
else if (strcmp("startup", db) == 0)
_startup_locked = pid;
hash_add(th->th_dbs, db, &pid, sizeof(pid));
clicon_debug(1, "%s: locked by %u", db, pid);
return 0;
}
@ -929,16 +838,13 @@ text_lock(xmldb_handle xh,
*/
int
text_unlock(xmldb_handle xh,
char *db)
char *db)
{
// struct text_handle *th = handle(xh);
struct text_handle *th = handle(xh);
int zero = 0;
if (strcmp("running", db) == 0)
_running_locked = 0;
else if (strcmp("candidate", db) == 0)
_candidate_locked = 0;
else if (strcmp("startup", db) == 0)
_startup_locked = 0;
hash_add(th->th_dbs, db, &zero, sizeof(zero));
// hash_del(th->th_dbs, db);
return 0;
}
@ -952,14 +858,19 @@ int
text_unlock_all(xmldb_handle xh,
int pid)
{
// struct text_handle *th = handle(xh);
struct text_handle *th = handle(xh);
char **keys;
size_t klen;
int i;
int *val;
size_t vlen;
if (_running_locked == pid)
_running_locked = 0;
if (_candidate_locked == pid)
_candidate_locked = 0;
if (_startup_locked == pid)
_startup_locked = 0;
if ((keys = hash_keys(th->th_dbs, &klen)) == NULL)
return 0;
for(i = 0; i < klen; i++)
if ((val = hash_value(th->th_dbs, keys[i], &vlen)) != NULL &&
*val == pid)
hash_del(th->th_dbs, keys[i]);
return 0;
}
@ -974,14 +885,13 @@ int
text_islocked(xmldb_handle xh,
char *db)
{
// struct text_handle *th = handle(xh);
struct text_handle *th = handle(xh);
size_t vlen;
int *val;
if (strcmp("running", db) == 0)
return (_running_locked);
else if (strcmp("candidate", db) == 0)
return(_candidate_locked);
else if (strcmp("startup", db) == 0)
return(_startup_locked);
if ((val = hash_value(th->th_dbs, db, &vlen)) == NULL)
return 0;
return *val;
return 0;
}
@ -993,8 +903,8 @@ text_islocked(xmldb_handle xh,
* @retval 1 Yes it exists
*/
int
text_exists(xmldb_handle xh,
char *db)
text_exists(xmldb_handle xh,
char *db)
{
int retval = -1;
@ -1022,7 +932,7 @@ text_exists(xmldb_handle xh,
*/
int
text_delete(xmldb_handle xh,
char *db)
char *db)
{
int retval = -1;
char *filename = NULL;
@ -1049,7 +959,7 @@ text_delete(xmldb_handle xh,
*/
int
text_create(xmldb_handle xh,
char *db)
char *db)
{
int retval = -1;
struct text_handle *th = handle(xh);
@ -1078,6 +988,7 @@ text_plugin_exit(void)
return 0;
}
static const struct xmldb_api api;
static const struct xmldb_api api;
/*! plugin init function */
@ -1167,7 +1078,7 @@ main(int argc, char **argv)
if (argc < 5)
usage(argv[0]);
xpath = argc>5?argv[5]:NULL;
if (xmldb_get(h, db, xpath, &xt, NULL, NULL) < 0)
if (xmldb_get(h, db, xpath, &xt, NULL, 1, NULL) < 0)
goto done;
clicon_xml2file(stdout, xt, 0, 1);
}

View file

@ -39,8 +39,7 @@
/*
* Prototypes
*/
int text_get(xmldb_handle h, char *db, char *xpath,
cxobj **xtop, cxobj ***xvec, size_t *xlen);
int text_get(xmldb_handle h, char *db, char *xpath, int config, cxobj **xtop);
int text_put(xmldb_handle h, char *db, enum operation_type op, cxobj *xt);
int text_dump(FILE *f, char *dbfilename, char *rxkey);
int text_copy(xmldb_handle h, char *from, char *to);

View file

@ -56,7 +56,7 @@ use the web resource: http://clicon.org/ref/index.html
## How is configuration data stored?
Configuration data is stored in an XML datastore. The default is a
text-based addatastore, but there also exists a key-value datastore
text-based datastore, but there also exists a key-value datastore
using qdbm. In the example the datastore are regular files found in
/usr/local/var/routing/.
@ -110,13 +110,33 @@ and then invoke it from a client using
ssh -s netconf <host>
```
## How do I use restconf?
You can access clixon via REST API using restconf, such as using
curl. GET, PUT, POST are supported.
You need a web-server, such as nginx, and start a restconf fcgi
daemon, clixon_restconf. Read more in the restconf docs.
Example:
```
curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=eth9/type
[
{
"type": "eth"
}
]
```
## How do I use notifications?
The example has a prebuilt notification stream called "ROUTING" that triggers every 10s.
You enable the notification either via the cli or via netconf:
You enable the notification either via the cli:
```
cli> notify
cli> Routing notification
Routing notification
cli>
```
or via netconf:
```
clixon_netconf -qf /usr/local/etc/routing.conf
<rpc><create-subscription><stream>ROUTING</stream></create-subscription></rpc>]]>]]>
@ -141,7 +161,7 @@ backend. It has a 'transaction_data td' argument which is used to fetch
information on added, deleted and changed entries. You access this
information using access functions as defined in clixon_backend_transaction.h
## How do i check what has changed on commit?
## How do I check what has changed on commit?
You use XPATHs on the XML trees in the transaction commit callback.
Suppose you want to print all added interfaces:
```
@ -181,3 +201,12 @@ Check for inconsistencies in the XML trees and if they fail, make an clicon_err(
return -1;
The validation or commit will then be aborted.
## How do I write a state data callback function?
Netconf <get> and restconf GET also returns state data, in contrast to
config data. In YANG state data is specified with "config false;".
To return state data, you need to write a backend state data callback
with the name "plugin_statedata()" where you return an XML tree.
Please look at the example for an example on how to write a state data callback.

View file

@ -81,8 +81,28 @@ cli> downcall "This is a string"
This is a string
```
## State data
Netconf <get> and restconf GET also returns state data, in contrast to
config data.
In YANG state data is specified with "config false;". In the example, interface-state is state data.
To return state data, you need to write a backend state data callback
with the name "plugin_statedata" where you return an XML tree with
state. This is then merged with config data by the system.
pA static example of returning state data is in the example. Note that
a real example would poll or get the interface counters via a system
call, as well as use the "xpath" argument to identify the requested
state data.
## Run as docker container
```
cd docker
# look in README
```
```

View file

@ -1,4 +1,4 @@
module ietf-ip {
module ietf-ip {
namespace "urn:ietf:params:xml:ns:yang:ietf-ip";
prefix ip;

View file

@ -129,6 +129,40 @@ routing_downcall(clicon_handle h,
cprintf(cbret, "<rpc-reply><ok>%s</ok></rpc-reply>", xml_body(xe));
return 0;
}
/*! Called to get state data from plugin
* @param[in] h Clicon handle
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[in] xtop XML tree, <config/> on entry.
* @retval 0 OK
* @retval -1 Error
* @see xmldb_get
*/
int
plugin_statedata(clicon_handle h,
char *xpath,
cxobj *xstate)
{
int retval = -1;
cxobj **xvec = NULL;
/* Example of (static) statedata, real code would poll state */
if (0 && (xml_parse("<interfaces-state><interface>"
"<name>eth0</name>"
"<type>eth</type>"
"<admin-status>up</admin-status>"
"<oper-status>up</oper-status>"
"<if-index>42</if-index>"
"<speed>1000000000</speed>"
"</interface></interfaces-state>", xstate)) < 0)
goto done;
retval = 0;
done:
if (xvec)
free(xvec);
return retval;
}
/*
* Plugin initialization
*/
@ -139,10 +173,11 @@ plugin_init(clicon_handle h)
if (notification_timer_setup(h) < 0)
goto done;
if (backend_netconf_register_callback(h, routing_downcall,
NULL,
"myrouting"/* Xml tag when callback is made */
) < 0)
/* Register callback for netconf application-specific rpc call */
if (backend_rpc_cb_register(h, routing_downcall,
NULL,
"myrouting"/* Xml tag when callback is made */
) < 0)
goto done;
retval = 0;
done:

View file

@ -83,6 +83,7 @@ mycallback(clicon_handle h, cvec *cvv, cvec *argv)
if (clicon_rpc_get_config(h, "running","/interfaces/interface[name=eth0]",
&xret) < 0)
goto done;
xml_print(stdout, xret);
retval = 0;
done:

View file

@ -52,6 +52,7 @@ int clicon_rpc_copy_config(clicon_handle h, char *db1, char *db2);
int clicon_rpc_delete_config(clicon_handle h, char *db);
int clicon_rpc_lock(clicon_handle h, char *db);
int clicon_rpc_unlock(clicon_handle h, char *db);
int clicon_rpc_get(clicon_handle h, char *xpath, cxobj **xret);
int clicon_rpc_close_session(clicon_handle h);
int clicon_rpc_kill_session(clicon_handle h, int session_id);
int clicon_rpc_validate(clicon_handle h, char *db);

View file

@ -36,6 +36,23 @@
#ifndef _CLIXON_STRING_H_
#define _CLIXON_STRING_H_
/* Struct used to map between int and strings. Typically used to map between
* values and their names. Note NULL terminated
* Example:
* @code
static const map_str2int atmap[] = {
{"One", 1},
{"Two", 2},
{NULL, -1}
};
* @endcode
*/
struct map_str2int{
char *ms_str;
int ms_int;
};
typedef struct map_str2int map_str2int;
/*! A malloc version that aligns on 4 bytes. To avoid warning from valgrind */
#define align4(s) (((s)/4)*4 + 4)
@ -59,6 +76,9 @@ char *clicon_strjoin (int argc, char **argv, char *delim);
int str2cvec(char *string, char delim1, char delim2, cvec **cvp);
int percent_encode(char *str, char **escp);
int percent_decode(char *esc, char **str);
const char *clicon_int2str(const map_str2int *mstab, int i);
int clicon_str2int(const map_str2int *mstab, char *str);
#ifndef HAVE_STRNDUP
char *clicon_strndup (const char *, size_t);
#endif /* ! HAVE_STRNDUP */

View file

@ -128,6 +128,7 @@ int clicon_xml_parse_file(int fd, cxobj **xml_top, char *endtag);
#define clicon_xml_parse_string(str, x) clicon_xml_parse_str((*str), x)
int clicon_xml_parse_str(char *str, cxobj **xml_top);
int clicon_xml_parse(cxobj **cxtop, char *format, ...);
int xml_parse(char *str, cxobj *x_up);
int xmltree2cbuf(cbuf *cb, cxobj *x, int level);
int xml_copy(cxobj *x0, cxobj *x1);

View file

@ -75,12 +75,10 @@ typedef int (xmldb_getopt_t)(xmldb_handle xh, char *optname, void **value);
typedef int (xmldb_setopt_t)(xmldb_handle xh, char *optname, void *value);
/* Type of xmldb get function */
typedef int (xmldb_get_t)(xmldb_handle xh, char *db, char *xpath,
cxobj **xtop, cxobj ***xvec, size_t *xlen);
typedef int (xmldb_get_t)(xmldb_handle xh, char *db, char *xpath, int config, cxobj **xtop);
/* Type of xmldb put function */
typedef int (xmldb_put_t)(xmldb_handle xh, char *db, enum operation_type op,
cxobj *xt);
typedef int (xmldb_put_t)(xmldb_handle xh, char *db, enum operation_type op, cxobj *xt);
/* Type of xmldb copy function */
typedef int (xmldb_copy_t)(xmldb_handle xh, char *from, char *to);
@ -135,12 +133,12 @@ struct xmldb_api{
int xmldb_plugin_load(clicon_handle h, char *filename);
int xmldb_plugin_unload(clicon_handle h);
int xmldb_validate_db(char *db);
int xmldb_connect(clicon_handle h);
int xmldb_disconnect(clicon_handle h);
int xmldb_getopt(clicon_handle h, char *optname, void **value);
int xmldb_setopt(clicon_handle h, char *optname, void *value);
int xmldb_get(clicon_handle h, char *db, char *xpath,
cxobj **xtop, cxobj ***xvec, size_t *xlen);
int xmldb_get(clicon_handle h, char *db, char *xpath, int config, cxobj **xtop);
int xmldb_put(clicon_handle h, char *db, enum operation_type op, cxobj *xt);
int xmldb_copy(clicon_handle h, char *from, char *to);
int xmldb_lock(clicon_handle h, char *db, int pid);

View file

@ -48,7 +48,6 @@ enum {
LVXML_VECVAL2, /* key: a.b.0{x=1} -> <a><x>1</x></a> och */
};
/*
* Prototypes
*/
@ -64,12 +63,16 @@ int xml_diff(yang_spec *yspec, cxobj *xt1, cxobj *xt2,
int yang2api_path_fmt(yang_stmt *ys, int inclkey, char **api_path_fmt);
int api_path_fmt2api_path(char *api_path_fmt, cvec *cvv, char **api_path);
int api_path_fmt2xpath(char *api_path_fmt, cvec *cvv, char **xpath);
int xml_tree_prune_flagged(cxobj *xt, int flag, int test, int *upmark);
int xml_tree_prune_flagged_sub(cxobj *xt, int flag, int test, int *upmark);
int xml_tree_prune_flagged(cxobj *xt, int flag, int test);
int xml_default(cxobj *x, void *arg);
int xml_order(cxobj *x, void *arg);
int xml_sanity(cxobj *x, void *arg);
int xml_non_config_data(cxobj *xt, void *arg);
int xml_spec_populate(cxobj *x, void *arg);
int api_path2xpath_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath);
int api_path2xpath(yang_spec *yspec, char *api_path, cbuf *xpath);
int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop, cxobj **xpathp, yang_node **ypathp);
int xml_merge(cxobj *x0, cxobj *x1, yang_spec *yspec);
#endif /* _CLIXON_XML_MAP_H_ */

View file

@ -199,7 +199,7 @@ char *ytype_prefix(yang_stmt *ys);
char *ytype_id(yang_stmt *ys);
yang_stmt *ys_module(yang_stmt *ys);
yang_spec *ys_spec(yang_stmt *ys);
yang_stmt *ys_module_import(yang_stmt *ymod, char *prefix);
yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix);
yang_stmt *yang_find(yang_node *yn, int keyword, char *argument);
yang_stmt *yang_find_syntax(yang_node *yn, char *argument);
yang_stmt *yang_find_topnode(yang_spec *ysp, char *name);

View file

@ -291,7 +291,7 @@ hash_del(clicon_hash_t *hash,
return 0;
}
/*! Return vector of keys in has table
/*! Return vector of keys in hash table
*
* @param[in] hash Hash table
* @param[out] nkeys Size of key vector

View file

@ -230,16 +230,22 @@ clicon_rpc_generate_error(cxobj *xerr)
* @param[in] h CLICON handle
* @param[in] db Name of database
* @param[in] xpath XPath (or "")
* @param[out] xt XML tree. must be freed by caller with xml_free
* @param[out] xt XML tree. Free with xml_free.
* Either <config> or <rpc-error>.
* @retval 0 OK
* @retval -1 Error, fatal or xml
* @code
* cxobj *xt = NULL;
* if (clicon_rpc_get_config(h, "running", "/", &xt) < 0)
* err;
* if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){
* clicon_rpc_generate_error(xerr);
* err;
* }
* if (xt)
* xml_free(xt);
* @endcode
* @see clicon_rpc_generate_error
*/
int
clicon_rpc_get_config(clicon_handle h,
@ -251,7 +257,6 @@ clicon_rpc_get_config(clicon_handle h,
struct clicon_msg *msg = NULL;
cbuf *cb = NULL;
cxobj *xret = NULL;
cxobj *xerr;
cxobj *xd;
if ((cb = cbuf_new()) == NULL)
@ -264,11 +269,10 @@ clicon_rpc_get_config(clicon_handle h,
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
if ((xd = xpath_first(xret, "//data/config")) == NULL)
/* Send xml error back: first check error, then ok */
if ((xd = xpath_first(xret, "/rpc-reply/rpc-error")) != NULL)
xd = xml_parent(xd); /* point to rpc-reply */
else if ((xd = xpath_first(xret, "/rpc-reply/data/config")) == NULL)
if ((xd = xml_new("config", NULL)) == NULL)
goto done;
if (xt){
@ -472,6 +476,70 @@ clicon_rpc_unlock(clicon_handle h,
return retval;
}
/*! Get database configuration and state data
* Same as clicon_proto_change just with a cvec instead of lvec
* @param[in] h CLICON handle
* @param[in] xpath XPath (or "")
* @param[out] xt XML tree. Free with xml_free.
* Either <config> or <rpc-error>.
* @retval 0 OK
* @retval -1 Error, fatal or xml
* @code
* cxobj *xt = NULL;
* if (clicon_rpc_get(h, "/", &xt) < 0)
* err;
* if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){
* clicon_rpc_generate_error(xerr);
* err;
* }
* if (xt)
* xml_free(xt);
* @endcode
* @see clicon_rpc_generate_error
*/
int
clicon_rpc_get(clicon_handle h,
char *xpath,
cxobj **xt)
{
int retval = -1;
struct clicon_msg *msg = NULL;
cbuf *cb = NULL;
cxobj *xret = NULL;
cxobj *xd;
if ((cb = cbuf_new()) == NULL)
goto done;
cprintf(cb, "<rpc><get>");
if (xpath && strlen(xpath))
cprintf(cb, "<filter type=\"xpath\" select=\"%s\"/>", xpath);
cprintf(cb, "</get></rpc>");
if ((msg = clicon_msg_encode("%s", cbuf_get(cb))) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
/* Send xml error back: first check error, then ok */
if ((xd = xpath_first(xret, "/rpc-reply/rpc-error")) != NULL)
xd = xml_parent(xd); /* point to rpc-reply */
else if ((xd = xpath_first(xret, "/rpc-reply/data/config")) == NULL)
if ((xd = xml_new("config", NULL)) == NULL)
goto done;
if (xt){
if (xml_rm(xd) < 0)
goto done;
*xt = xd;
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
if (xret)
xml_free(xret);
if (msg)
free(msg);
return retval;
}
/*! Close a (user) session
* @param[in] h CLICON handle
*/

View file

@ -333,6 +333,43 @@ str2cvec(char *string,
goto done;
}
/*! Map from int to string using str2int map
* @param[in] ms String, integer map
* @param[in] i Input integer
* @retval str String value
* @retval NULL Error, not found
* @note linear search
*/
const char *
clicon_int2str(const map_str2int *mstab,
int i)
{
const struct map_str2int *ms;
for (ms = &mstab[0]; ms->ms_str; ms++)
if (ms->ms_int == i)
return ms->ms_str;
return NULL;
}
/*! Map from string to int using str2int map
* @param[in] ms String, integer map
* @param[in] str Input string
* @retval int Value
* @retval -1 Error, not found
* @note linear search
*/
int
clicon_str2int(const map_str2int *mstab,
char *str)
{
const struct map_str2int *ms;
for (ms = &mstab[0]; ms->ms_str; ms++)
if (strcmp(ms->ms_str, str) == 0)
return ms->ms_int;
return -1;
}
/*! strndup() for systems without it, such as xBSD
*/

View file

@ -50,6 +50,7 @@
/* clixon */
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_string.h"
#include "clixon_queue.h"
#include "clixon_xml.h"
#include "clixon_xml_parse.h"
@ -82,14 +83,8 @@ struct xml{
cg_var *x_cv; /* If body this contains the typed value */
};
/* Type to string conversion */
struct map_str2int{
char *ms_str;
enum cxobj_type ms_type;
};
/* Mapping between xml type <--> string */
static const struct map_str2int xsmap[] = {
static const map_str2int xsmap[] = {
{"error", CX_ERROR},
{"element", CX_ELMNT},
{"attr", CX_ATTR},
@ -104,12 +99,7 @@ static const struct map_str2int xsmap[] = {
char *
xml_type2str(enum cxobj_type type)
{
const struct map_str2int *xs;
for (xs = &xsmap[0]; xs->ms_str; xs++)
if (xs->ms_type == type)
return xs->ms_str;
return NULL;
return (char*)clicon_int2str(xsmap, type);
}
/*
@ -476,9 +466,14 @@ xml_childvec_get(cxobj *x)
*
* @param[in] name Name of new
* @param[in] xp The parent where the new xml node should be inserted
*
* @retval created xml object if successful
* @retval NULL if error and clicon_err() called
* @retval xml Created xml object if successful
* @retval NULL Error and clicon_err() called
* @code
* cxobj *x;
* if ((x = xml_new(name, xparent)) == NULL)
* err;
* @endcode
* @see xml_new_spec Also sets yang spec.
*/
cxobj *
xml_new(char *name,
@ -521,7 +516,6 @@ xml_new_spec(char *name,
return x;
}
void *
xml_spec(cxobj *x)
{
@ -971,10 +965,12 @@ clicon_xml2cbuf(cbuf *cb,
return 0;
}
/*! Internal xml parsing function.
/*! Basic xml parsing function.
* @param[in] str Pointer to string containing XML definition.
* @param[out] xtop Top of XML parse tree. Assume created.
* @see clicon_xml_parse_file clicon_xml_parse_string
*/
static int
int
xml_parse(char *str,
cxobj *x_up)
{
@ -1191,7 +1187,7 @@ clicon_xml_parse_str(char *str,
*/
int
clicon_xml_parse(cxobj **cxtop,
char *format, ...)
char *format, ...)
{
int retval = -1;
va_list args;
@ -1355,6 +1351,10 @@ cxvec_append(cxobj *x,
* @param[in] type matching type or -1 for any
* @param[in] fn Callback
* @param[in] arg Argument
* @retval -1 Error, aborted at first error encounter
* @retval 0 OK, all nodes traversed
* @retval n OK, aborted at first encounter of first match
*
* @code
* int x_fn(cxobj *x, void *arg)
* {
@ -1363,7 +1363,7 @@ cxvec_append(cxobj *x,
* xml_apply(xn, CX_ELMNT, x_fn, NULL);
* @endcode
* @note do not delete or move around any children during this function
* @note It does not apply fn to the root node,..
* @note return value > 0 aborts the traversal
* @see xml_apply0 including top object
*/
int
@ -1373,13 +1373,19 @@ xml_apply(cxobj *xn,
void *arg)
{
int retval = -1;
cxobj *x = NULL;
cxobj *x;
int ret;
x = NULL;
while ((x = xml_child_each(xn, x, type)) != NULL) {
if (fn(x, arg) < 0)
goto done;
if (xml_apply(x, type, fn, arg) < 0)
if ((ret = xml_apply(x, type, fn, arg)) < 0)
goto done;
if (ret > 0){
retval = ret;
goto done;
}
}
retval = 0;
done:
@ -1387,7 +1393,11 @@ xml_apply(cxobj *xn,
}
/*! Apply a function call on top object and all xml node children recursively
* @retval -1 Error, aborted at first error encounter
* @retval 0 OK, all nodes traversed
* @retval n OK, aborted at first encounter of first match
* @see xml_apply not including top object
*/
int
xml_apply0(cxobj *xn,
@ -1396,10 +1406,14 @@ xml_apply0(cxobj *xn,
void *arg)
{
int retval = -1;
int ret;
if (fn(xn, arg) < 0)
if ((ret = fn(xn, arg)) < 0)
goto done;
retval = xml_apply(xn, type, fn, arg);
if (ret > 0)
retval = ret;
else
retval = xml_apply(xn, type, fn, arg);
done:
return retval;
}
@ -1412,6 +1426,9 @@ xml_apply0(cxobj *xn,
* @param[in] xn XML node
* @param[in] fn Callback
* @param[in] arg Argument
* @retval -1 Error, aborted at first error encounter
* @retval 0 OK, all nodes traversed
* @retval n OK, aborted at first encounter of first match
* @code
* int x_fn(cxobj *x, void *arg)
* {
@ -1430,12 +1447,17 @@ xml_apply_ancestor(cxobj *xn,
{
int retval = -1;
cxobj *xp = NULL;
int ret;
while ((xp = xml_parent(xn)) != NULL) {
if (fn(xp, arg) < 0)
goto done;
if (xml_apply_ancestor(xp, fn, arg) < 0)
if ((ret = xml_apply_ancestor(xp, fn, arg)) < 0)
goto done;
if (ret > 0){
retval = ret;
goto done;
}
xn = xp;
}
retval = 0;

View file

@ -169,6 +169,23 @@ xmldb_plugin_unload(clicon_handle h)
return retval;
}
/*! Validate database name
* @param[in] db Name of database
* @param[out] xret Return value as cligen buffer containing xml netconf return
* @retval 0 OK
* @retval -1 Failed validate, xret set to error
*/
int
xmldb_validate_db(char *db)
{
if (strcmp(db, "running") != 0 &&
strcmp(db, "candidate") != 0 &&
strcmp(db, "startup") != 0 &&
strcmp(db, "tmp") != 0)
return -1;
return 0;
}
/*! Connect to a datastore plugin, allocate handle to be used in API calls
* @param[in] h Clicon handle
* @retval 0 OK
@ -305,36 +322,25 @@ xmldb_setopt(clicon_handle h,
* @param[in] h Clicon handle
* @param[in] dbname Name of database to search in (filename including dir path
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[out] xtop Single XML tree which xvec points to. Free with xml_free()
* @param[out] xvec Vector of xml trees. Free after use.
* @param[out] xlen Length of vector.
* @param[in] config If set only configuration data, else also state
* @param[out] xtop Single XML tree. Free with xml_free()
* @retval 0 OK
* @retval -1 Error
* @code
* cxobj *xt;
* cxobj **xvec;
* size_t xlen;
* if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]",
* &xt, &xvec, &xlen) < 0)
* if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]", 1, &xt) < 0)
* err;
* for (i=0; i<xlen; i++){
* xn = xv[i];
* ...
* }
* xml_free(xt);
* free(xvec);
* @endcode
* @note if xvec is given, then purge tree, if not return whole tree.
* @see xpath_vec
* @see xmldb_get
*/
int
xmldb_get(clicon_handle h,
char *db,
char *xpath,
cxobj **xtop,
cxobj ***xvec,
size_t *xlen)
int config,
cxobj **xtop)
{
int retval = -1;
xmldb_handle xh;
@ -352,7 +358,7 @@ xmldb_get(clicon_handle h,
clicon_err(OE_DB, 0, "Not connected to datastore plugin");
goto done;
}
retval = xa->xa_get_fn(xh, db, xpath, xtop, xvec, xlen);
retval = xa->xa_get_fn(xh, db, xpath, config, xtop);
#if DEBUG
if (retval == 0) {
cbuf *cb = cbuf_new();
@ -366,14 +372,12 @@ xmldb_get(clicon_handle h,
return retval;
}
/*! Modify database provided an xml tree and an operation
/*! Modify database given an xml tree and an operation
*
* @param[in] h CLICON handle
* @param[in] db running or candidate
* @param[in] op Top-level operation, can be superceded by other op in tree
* @param[in] xt xml-tree. Top-level symbol is dummy
* @param[in] op OP_MERGE: just add it.
* OP_REPLACE: first delete whole database
* OP_NONE: operation attribute in xml determines operation
* @retval 0 OK
* @retval -1 Error
* The xml may contain the "operation" attribute which defines the operation.
@ -384,6 +388,9 @@ xmldb_get(clicon_handle h,
* if (xmldb_put(xh, "running", OP_MERGE, xt) < 0)
* err;
* @endcode
* @note that you can add both config data and state data. In comparison,
* xmldb_get has a parameter to get config data only.
*
*/
int
xmldb_put(clicon_handle h,
@ -581,7 +588,7 @@ xmldb_islocked(clicon_handle h,
clicon_err(OE_DB, 0, "Not connected to datastore plugin");
goto done;
}
retval =xa->xa_islocked_fn(xh, db);
retval = xa->xa_islocked_fn(xh, db);
done:
return retval;
}

View file

@ -1084,23 +1084,25 @@ api_path_fmt2xpath(char *api_path_fmt,
return retval;
}
/*! Prune everything that does not pass test
/*! Prune everything that does not pass test or have at least a child* does not
* @param[in] xt XML tree with some node marked
* @param[in] flag Which flag to test for
* @param[in] test 1: test that flag is set, 0: test that flag is not set
* @param[out] upmark Set if a child (recursively) has marked set.
* The function removes all branches that does not a child that pass the test
* The function removes all branches that does not pass the test
* Purge all nodes that dont have MARK flag set recursively.
* Save all nodes that is MARK:ed or have at least one (grand*)child that is MARKed
* @code
* xml_tree_prune_flagged(xt, XML_FLAG_MARK, 1, NULL);
* xml_tree_prune_flagged_sub(xt, XML_FLAG_MARK, 1, NULL);
* @endcode
* @note This function seems a little too complex semantics
* @see xml_tree_prune_flagged for a simpler variant
*/
int
xml_tree_prune_flagged(cxobj *xt,
int flag,
int test,
int *upmark)
xml_tree_prune_flagged_sub(cxobj *xt,
int flag,
int test,
int *upmark)
{
int retval = -1;
int submark;
@ -1117,6 +1119,7 @@ xml_tree_prune_flagged(cxobj *xt,
xprev = x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if (xml_flag(x, flag) == test?flag:0){
/* Pass test */
mark++;
xprev = x;
continue; /* mark and stop here */
@ -1131,7 +1134,7 @@ xml_tree_prune_flagged(cxobj *xt,
continue;
}
}
if (xml_tree_prune_flagged(x, flag, test, &submark) < 0)
if (xml_tree_prune_flagged_sub(x, flag, test, &submark) < 0)
goto done;
/* if xt is list and submark anywhere, then key subs are also marked
*/
@ -1167,6 +1170,43 @@ xml_tree_prune_flagged(cxobj *xt,
return retval;
}
/*! Prune everything that passes test
* @param[in] xt XML tree with some node marked
* @param[in] flag Which flag to test for
* @param[in] test 1: test that flag is set, 0: test that flag is not set
* The function removes all branches that does not pass test
* @code
* xml_tree_prune_flagged(xt, XML_FLAG_MARK, 1, NULL);
* @endcode
*/
int
xml_tree_prune_flagged(cxobj *xt,
int flag,
int test)
{
int retval = -1;
cxobj *x;
cxobj *xprev;
x = NULL;
xprev = x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if (xml_flag(x, flag) == test?flag:0){ /* Pass test means purge */
if (xml_purge(x) < 0)
goto done;
x = xprev;
continue;
}
if (xml_tree_prune_flagged(x, flag, test) < 0)
goto done;
xprev = x;
}
retval = 0;
done:
return retval;
}
/*! Add default values (if not set)
* @param[in] xt XML tree with some node marked
*/
@ -1293,10 +1333,6 @@ xml_sanity(cxobj *xt,
goto done;
}
name = xml_name(xt);
if (ys==NULL){
clicon_err(OE_XML, 0, "No spec for xml node %s", name);
goto done;
}
if (strstr(ys->ys_argument, name)==NULL){
clicon_err(OE_XML, 0, "xml node name '%s' does not match yang spec arg '%s'",
name, ys->ys_argument);
@ -1307,6 +1343,69 @@ xml_sanity(cxobj *xt,
return retval;
}
/*! Mark all nodes that are not configure data and set return
* @param[in] xt XML tree
* @param[out] arg If set, set to 1 as int* if not config data
*/
int
xml_non_config_data(cxobj *xt,
void *arg) /* Set to 1 if state node */
{
int retval = -1;
yang_stmt *ys;
if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){
clicon_log(LOG_WARNING, "%s: no xml_spec(%s)", __FUNCTION__, xml_name(xt));
retval = 0;
goto done;
}
if (!yang_config(ys)){ /* config == false means state data: mark for remove */
xml_flag_set(xt, XML_FLAG_MARK);
if (arg)
(*(int*)arg) = 1;
}
retval = 0;
done:
return retval;
}
/*! Add yang specification backpoint to XML node
* @param[in] xt XML tree node
* @note This should really be unnecessary since yspec should be set on creation
* @code
* xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec)
* @endcode
*/
int
xml_spec_populate(cxobj *x,
void *arg)
{
int retval = -1;
yang_spec *yspec = (yang_spec*)arg;
char *name;
yang_stmt *y; /* yang node */
cxobj *xp; /* xml parent */
yang_stmt *yp; /* parent yang */
name = xml_name(x);
if ((xp = xml_parent(x)) != NULL &&
(yp = xml_spec(xp)) != NULL)
y = yang_find_syntax((yang_node*)yp, xml_name(x));
else
y = yang_find_topnode(yspec, name); /* still NULL for config */
if (y==NULL){
clicon_err(OE_XML, EBADF, "yang spec not found for xml node '%s' xml parent name: '%s' yangspec:'%s']",
name,
xp?xml_name(xp):"", yp?yp->ys_argument:"");
goto done;
}
xml_spec_set(x, y);
retval = 0;
done:
return retval;
}
/*! Translate from restconf api-path in cvv form to xml xpath
* eg a/b=c -> a/[b=c]
* @param[in] yspec Yang spec
@ -1617,3 +1716,195 @@ api_path2xml(char *api_path,
free(vec);
return retval;
}
/*! Given a modification tree, check existing matching child in the base tree
* param[in] x0 Base tree node
* param[in] x1c Modification tree child
* param[in] yc Yang spec of tree child
* param[out] x0cp Matching base tree child (if any)
*/
static int
match_base_child(cxobj *x0,
cxobj *x1c,
yang_stmt *yc,
cxobj **x0cp)
{
int retval = -1;
cxobj *x0c = NULL;
char *keyname;
cvec *cvk = NULL;
cg_var *cvi;
char *b0;
char *b1;
yang_stmt *ykey;
char *cname;
int ok;
char *x1bstr; /* body string */
cname = xml_name(x1c);
switch (yc->ys_keyword){
case Y_LEAF_LIST: /* Match with name and value */
x1bstr = xml_body(x1c);
x0c = NULL;
while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL) {
if (strcmp(cname, xml_name(x0c)) == 0 &&
strcmp(xml_body(x0c), x1bstr)==0)
break;
}
break;
case Y_LIST: /* Match with key values */
if ((ykey = yang_find((yang_node*)yc, Y_KEY, NULL)) == NULL){
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
__FUNCTION__, yc->ys_argument);
goto done;
}
/* The value is a list of keys: <key>[ <key>]* */
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
goto done;
x0c = NULL;
while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL) {
if (strcmp(xml_name(x0c), cname))
continue;
cvi = NULL;
ok = 0;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
ok = 1; /* if we come here */
if ((b0 = xml_find_body(x0c, keyname)) == NULL)
break; /* error case */
if ((b1 = xml_find_body(x1c, keyname)) == NULL)
break; /* error case */
if (strcmp(b0, b1))
break;
ok = 2; /* and reaches here for all keynames, x0c is found. */
}
if (ok == 2)
break;
}
break;
default: /* Just match with name */
x0c = xml_find(x0, cname);
break;
}
*x0cp = x0c;
retval = 0;
done:
if (cvk)
cvec_free(cvk);
return retval;
}
/*! Merge a base tree x0 with x1 with yang spec y
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
* @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL
* @param[in] x0p Parent of x0
* @param[in] x1 xml tree which modifies base
* Assume x0 and x1 are same on entry and that y is the spec
* @see put in clixon_keyvalue.c
*/
static int
xml_merge1(cxobj *x0,
yang_node *y0,
cxobj *x0p,
cxobj *x1)
{
int retval = -1;
char *x1name;
char *x1cname; /* child name */
cxobj *x0c; /* base child */
cxobj *x0b; /* base body */
cxobj *x1c; /* mod child */
char *x1bstr; /* mod body string */
yang_stmt *yc; /* yang child */
assert(x1 && xml_type(x1) == CX_ELMNT);
assert(y0);
x1name = xml_name(x1);
if (y0->yn_keyword == Y_LEAF_LIST || y0->yn_keyword == Y_LEAF){
x1bstr = xml_body(x1);
if (x0==NULL){
if ((x0 = xml_new_spec(x1name, x0p, y0)) == NULL)
goto done;
if (x1bstr){ /* empty type does not have body */
if ((x0b = xml_new("body", x0)) == NULL)
goto done;
xml_type_set(x0b, CX_BODY);
}
}
if (x1bstr){
if ((x0b = xml_body_get(x0)) == NULL){
if ((x0b = xml_new("body", x0)) == NULL)
goto done;
xml_type_set(x0b, CX_BODY);
}
if (xml_value_set(x0b, x1bstr) < 0)
goto done;
}
} /* if LEAF|LEAF_LIST */
else { /* eg Y_CONTAINER, Y_LIST */
if (x0==NULL){
if ((x0 = xml_new_spec(x1name, x0p, y0)) == NULL)
goto done;
}
/* Loop through children of the modification tree */
x1c = NULL;
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
x1cname = xml_name(x1c);
/* Get yang spec of the child */
if ((yc = yang_find_syntax(y0, x1cname)) == NULL){
clicon_err(OE_YANG, errno, "No yang node found: %s", x1cname);
goto done;
}
/* See if there is a corresponding node in the base tree */
x0c = NULL;
if (yc && match_base_child(x0, x1c, yc, &x0c) < 0)
goto done;
if (xml_merge1(x0c, (yang_node*)yc, x0, x1c) < 0)
goto done;
}
} /* else Y_CONTAINER */
// ok:
retval = 0;
done:
return retval;
}
/*! Merge XML trees x1 into x0 according to yang spec yspec
* @note both x0 and x1 need to be top-level trees
* @see text_modify_top as more generic variant (in datastore text)
*/
int
xml_merge(cxobj *x0,
cxobj *x1,
yang_spec *yspec)
{
int retval = -1;
char *x1cname; /* child name */
cxobj *x0c; /* base child */
cxobj *x1c; /* mod child */
yang_stmt *yc;
/* Assure top-levels are 'config' */
assert(x0 && strcmp(xml_name(x0),"config")==0);
assert(x1 && strcmp(xml_name(x1),"config")==0);
/* Loop through children of the modification tree */
x1c = NULL;
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
x1cname = xml_name(x1c);
/* Get yang spec of the child */
if ((yc = yang_find_topnode(yspec, x1cname)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
/* See if there is a corresponding node in the base tree */
if (match_base_child(x0, x1c, yc, &x0c) < 0)
goto done;
if (xml_merge1(x0c, (yang_node*)yc, x0, x1c) < 0)
goto done;
}
retval = 0;
done:
return retval;
}

View file

@ -100,6 +100,7 @@ in
/* clicon */
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_string.h"
#include "clixon_xml.h"
#include "clixon_xsl.h"
@ -130,13 +131,8 @@ enum axis_type{
A_DESCENDANT_OR_SELF, /* actually descendant-or-self */
};
struct map_str2int{
char *ms_str; /* string as in 4.2.4 in RFC 6020 */
int ms_int;
};
/* Mapping between axis type string <--> int */
static const struct map_str2int atmap[] = {
static const map_str2int axismap[] = {
{"self", A_SELF},
{"child", A_CHILD},
{"parent", A_PARENT},
@ -160,19 +156,6 @@ struct xpath_element{
static int xpath_split(char *xpathstr, char **pathexpr);
static char *axis_type2str(enum axis_type type) __attribute__ ((unused));
static char *
axis_type2str(enum axis_type type)
{
const struct map_str2int *at;
for (at = &atmap[0]; at->ms_str; at++)
if (at->ms_int == type)
return at->ms_str;
return NULL;
}
static int
xpath_print(FILE *f, struct xpath_element *xplist)
{
@ -180,7 +163,7 @@ xpath_print(FILE *f, struct xpath_element *xplist)
struct xpath_predicate *xp;
for (xe=xplist; xe; xe=xe->xe_next){
fprintf(f, "\t:%s %s ", axis_type2str(xe->xe_type),
fprintf(f, "\t:%s %s ", clicon_int2str(axismap, xe->xe_type),
xe->xe_str?xe->xe_str:"");
for (xp=xe->xe_predicate; xp; xp=xp->xp_next)
fprintf(f, "[%s]", xp->xp_expr);
@ -598,7 +581,7 @@ xpath_find(struct xpath_element *xe,
}
#if 0
fprintf(stderr, "%s: %s: \"%s\"\n", __FUNCTION__,
axis_type2str(xe->xe_type), xe->xe_str?xe->xe_str:"");
clicon_int2str(axismap, xe->xe_type), xe->xe_str?xe->xe_str:"");
#endif
switch (xe->xe_type){
case A_SELF:
@ -942,12 +925,12 @@ xpath_each(cxobj *cxtop,
* @retval -1 error.
*
* @code
* cxobj **vec;
* size_t veclen;
* if (xpath_vec(cxtop, "//symbol/foo", &vec, &veclen) < 0)
* cxobj **xvec;
* size_t xlen;
* if (xpath_vec(cxtop, "//symbol/foo", &xvec, &xlen) < 0)
* goto err;
* for (i=0; i<veclen; i++){
* xn = vec[i];
* for (i=0; i<xlen; i++){
* xn = xvec[i];
* ...
* }
* free(vec);

View file

@ -71,25 +71,9 @@
#include "clixon_yang_type.h"
#include "clixon_yang_parse.h"
/* Instead of using dynamic type lookup, use a cache that is evaluated early
for static scope type binding */
#define YANG_TYPE_CACHE 1
/*
* Private data types
*/
/* Struct used to map between int and strings. Used for:
* - mapping yang types/typedefs (strings) and cligen types (ints).
* - mapping yang keywords (strings) and enum (clicon)
*/
struct map_str2int{
char *ms_str; /* string as in 4.2.4 in RFC 6020 */
int ms_int;
};
/* Mapping between yang keyword string <--> clicon constants */
static const struct map_str2int ykmap[] = {
static const map_str2int ykmap[] = {
{"anyxml", Y_ANYXML},
{"argument", Y_ARGUMENT},
{"augment", Y_AUGMENT},
@ -545,18 +529,10 @@ ys_flag_reset(yang_stmt *ys,
return 0;
}
/*! Translate from RFC 6020 keywords to printable string.
linear search,...
*/
char *
yang_key2str(int keyword)
{
const struct map_str2int *yk;
for (yk = &ykmap[0]; yk->ms_str; yk++)
if (yk->ms_int == keyword)
return yk->ms_str;
return NULL;
return (char*)clicon_int2str(ykmap, keyword);
}
/*! Find top module or sub-module. Note that ultimate top is yang spec
@ -635,29 +611,63 @@ ytype_prefix(yang_stmt *ys)
return prefix;
}
/*! Given a module and a prefix, find the import statement fo that prefix
/*! Given a yang statement and a prefix, return yang module to that prefix
* Note, not the other module but the proxy import statement only
* @param[in] ytop yang module
* @param[in] ys A yang statement
* @param[in] prefix prefix
* @retval ymod Yang module statement if found
* @retval NULL not found
*/
yang_stmt *
ys_module_import(yang_stmt *ymod,
char *prefix)
yang_find_module_by_prefix(yang_stmt *ys,
char *prefix)
{
yang_stmt *yimport = NULL;
yang_stmt *yimport;
yang_stmt *yprefix;
yang_stmt *my_ymod;
yang_stmt *ymod = NULL;
yang_spec *yspec;
assert(ymod->ys_keyword == Y_MODULE || ymod->ys_keyword == Y_SUBMODULE);
while ((yimport = yn_each((yang_node*)ymod, yimport)) != NULL) {
if ((my_ymod = ys_module(ys)) == NULL){
clicon_err(OE_YANG, 0, "My yang module not found");
goto done;
}
if ((yspec = ys_spec(my_ymod)) == NULL){
clicon_err(OE_YANG, 0, "My yang spec not found");
goto done;
}
if (my_ymod->ys_keyword != Y_MODULE &&
my_ymod->ys_keyword != Y_SUBMODULE){
clicon_err(OE_YANG, 0, "%s not module or sub-module", my_ymod->ys_argument);
goto done;
}
if ((yprefix = yang_find((yang_node*)my_ymod, Y_PREFIX, NULL)) != NULL &&
strcmp(yprefix->ys_argument, prefix) == 0){
ymod = my_ymod;
goto done;
}
yimport = NULL;
while ((yimport = yn_each((yang_node*)my_ymod, yimport)) != NULL) {
if (yimport->ys_keyword != Y_IMPORT)
continue;
if ((yprefix = yang_find((yang_node*)yimport, Y_PREFIX, NULL)) != NULL &&
strcmp(yprefix->ys_argument, prefix) == 0)
return yimport;
strcmp(yprefix->ys_argument, prefix) == 0){
break;
}
}
return NULL;
if (yimport){
if ((ymod = yang_find((yang_node*)yspec, Y_MODULE, yimport->ys_argument)) == NULL){
clicon_err(OE_YANG, 0, "No module or sub-module found with prefix %s",
yimport->ys_argument);
yimport = NULL;
goto done; /* unresolved */
}
}
done:
return ymod;
}
/*! string is quoted if it contains space or tab, needs double '' */
static int inline
quotedstring(char *s)
@ -1056,22 +1066,13 @@ ys_grouping_resolve(yang_stmt *ys,
yang_stmt **ygrouping0)
{
int retval = -1;
yang_stmt *yimport;
yang_spec *yspec;
yang_stmt *ymodule;
yang_stmt *ygrouping = NULL;
yang_node *yn;
yang_stmt *ymod;
/* find the grouping associated with argument and expand(?) */
if (prefix){ /* Go to top and find import that matches */
ymod = ys_module(ys);
if ((yimport = ys_module_import(ymod, prefix)) == NULL){
clicon_err(OE_DB, 0, "Prefix %s not defined not found", prefix);
goto done;
}
yspec = ys_spec(ys);
if ((ymodule = yang_find((yang_node*)yspec, Y_MODULE, yimport->ys_argument)) != NULL)
if ((ymodule = yang_find_module_by_prefix(ys, prefix)) != NULL)
ygrouping = yang_find((yang_node*)ymodule, Y_GROUPING, name);
}
else
@ -1087,7 +1088,7 @@ ys_grouping_resolve(yang_stmt *ys,
}
*ygrouping0 = ygrouping;
retval = 0;
done:
// done:
return retval;
}
@ -1266,8 +1267,8 @@ yang_expand(yang_node *yn)
* @param str String of yang statements
* @param name Log string, typically filename
* @param ysp Yang specification. Should ave been created by caller using yspec_new
* @retval 0 Everything OK
* @retval -1 Error encountered
* @retval ymod Top-level yang (sub)module
* @retval NULL Error encountered
* Calling order:
* yang_parse # Parse top-level yang module. Expand and populate yang tree
* yang_parse1 # Parse one yang module, go through its (sub)modules and parse them
@ -1283,7 +1284,7 @@ yang_parse_str(clicon_handle h,
yang_spec *yspec)
{
struct clicon_yang_yacc_arg yy = {0,};
yang_stmt *ym = NULL;
yang_stmt *ymod = NULL;
yy.yy_handle = h;
yy.yy_name = (char*)name;
@ -1312,10 +1313,10 @@ yang_parse_str(clicon_handle h,
if (yang_scan_exit(&yy) < 0)
goto done;
}
ym = yy.yy_module;
ymod = yy.yy_module;
done:
ystack_pop(&yy);
return ym;
return ymod; /* top-level (sub)module */
}
/*! Read an opened file into a string and call yang string parsing
@ -1326,8 +1327,8 @@ yang_parse_str(clicon_handle h,
* @param f Open file handle
* @param name Log string, typically filename
* @param ysp Yang specification. Should ave been created by caller using yspec_new
* @retval 0 Everything OK
* @retval -1 Error encountered
* @retval ymod Top-level yang (sub)module
* @retval NULL Error encountered
* The database symbols are inserted in alphabetical order.
* Calling order:
@ -1349,7 +1350,7 @@ yang_parse_file(clicon_handle h,
int i;
int c;
int len;
yang_stmt *ymodule = NULL;
yang_stmt *ymod = NULL;
clicon_debug(1, "Yang parse file: %s", name);
len = 1024; /* any number is fine */
@ -1373,12 +1374,12 @@ yang_parse_file(clicon_handle h,
}
buf[i++] = (char)(c&0xff);
} /* read a line */
if ((ymodule = yang_parse_str(h, buf, name, ysp)) < 0)
if ((ymod = yang_parse_str(h, buf, name, ysp)) < 0)
goto done;
done:
if (buf)
free(buf);
return ymodule;
return ymod; /* top-level (sub)module */
}
/*! No specific revision give. Match a yang file given dir and module
@ -1387,7 +1388,7 @@ yang_parse_file(clicon_handle h,
* @param[in] module Name of main YANG module.
* @param[out] fbuf Buffer containing filename
*
* @retval 1 Match founbd, Most recent entry returned in fbuf
* @retval 1 Match found, Most recent entry returned in fbuf
* @retval 0 No matching entry found
* @retval -1 Error
*/
@ -1401,17 +1402,20 @@ yang_parse_find_match(clicon_handle h,
struct dirent *dp = NULL;
int ndp;
cbuf *regex = NULL;
char *regexstr;
if ((regex = cbuf_new()) == NULL){
clicon_err(OE_YANG, errno, "cbuf_new");
goto done;
}
cprintf(regex, "^%s.*(.yang)$", module);
regexstr = cbuf_get(regex);
/* RFC 6020: The name of the file SHOULD be of the form:
module-or-submodule-name ['@' revision-date] ( '.yang' / '.yin' )
revision-date ::= 4DIGIT "-" 2DIGIT "-" 2DIGIT
*/
cprintf(regex, "^%s(@[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9])?(.yang)$",
module);
if ((ndp = clicon_file_dirent(yang_dir,
&dp,
regexstr,
cbuf_get(regex),
S_IFREG)) < 0)
goto done;
/* Entries are sorted, last entry should be most recent date */
@ -1436,8 +1440,8 @@ yang_parse_find_match(clicon_handle h,
* @param module Name of main YANG module. More modules may be parsed if imported
* @param revision Optional module revision date
* @param ysp Yang specification. Should ave been created by caller using yspec_new
* @retval 0 Everything OK
* @retval -1 Error encountered
* @retval ymod Top-level yang (sub)module
* @retval NULL Error encountered
* module-or-submodule-name ['@' revision-date] ( '.yang' / '.yin' )
* Calling order:
* yang_parse # Parse top-level yang module. Expand and populate yang tree
@ -1457,7 +1461,7 @@ yang_parse2(clicon_handle h,
FILE *f = NULL;
cbuf *fbuf = NULL;
char *filename;
yang_stmt *ys = NULL;
yang_stmt *ymod = NULL;
struct stat st;
int nr;
@ -1485,14 +1489,14 @@ yang_parse2(clicon_handle h,
clicon_err(OE_UNIX, errno, "fopen(%s)", filename);
goto done;
}
if ((ys = yang_parse_file(h, f, filename, ysp)) == NULL)
if ((ymod = yang_parse_file(h, f, filename, ysp)) == NULL)
goto done;
done:
if (fbuf)
cbuf_free(fbuf);
if (f)
fclose(f);
return ys;
return ymod; /* top-level (sub)module */
}
/*! Parse one yang module then go through (sub)modules and parse them recursively
@ -1501,9 +1505,9 @@ yang_parse2(clicon_handle h,
* @param yang_dir Directory where all YANG module files reside
* @param module Name of main YANG module. More modules may be parsed if imported
* @param revision Optional module revision date
* @param ysp Yang specification. Should ave been created by caller using yspec_new
* @retval 0 Everything OK
* @retval -1 Error encountered
* @param ysp Yang specification. Should have been created by caller using yspec_new
* @retval ymod Top-level yang (sub)module
* @retval NULL Error encountered
* Find a yang module file, and then recursively parse all its imported modules.
* Calling order:
* yang_parse # Parse top-level yang module. Expand and populate yang tree
@ -1521,15 +1525,15 @@ yang_parse1(clicon_handle h,
yang_spec *ysp)
{
yang_stmt *yi = NULL; /* import */
yang_stmt *ys;
yang_stmt *ymod;
yang_stmt *yrev;
char *modname;
char *subrevision;
if ((ys = yang_parse2(h, yang_dir, module, revision, ysp)) == NULL)
if ((ymod = yang_parse2(h, yang_dir, module, revision, ysp)) == NULL)
goto done;
/* go through all import statements of ysp (or its module) */
while ((yi = yn_each((yang_node*)ys, yi)) != NULL){
while ((yi = yn_each((yang_node*)ymod, yi)) != NULL){
if (yi->ys_keyword != Y_IMPORT)
continue;
modname = yi->ys_argument;
@ -1539,12 +1543,12 @@ yang_parse1(clicon_handle h,
subrevision = NULL;
if (yang_find((yang_node*)ysp, Y_MODULE, modname) == NULL)
if (yang_parse1(h, yang_dir, modname, subrevision, ysp) == NULL){
ys = NULL;
ymod = NULL;
goto done;
}
}
done:
return ys;
return ymod; /* top-level (sub)module */
}
/*! Parse top yang module including all its sub-modules. Expand and populate yang tree
@ -1574,22 +1578,21 @@ yang_parse(clicon_handle h,
yang_spec *ysp)
{
int retval = -1;
yang_stmt *ys;
yang_stmt *ymod; /* Top-level yang (sub)module */
/* Step 1: parse from text to yang parse-tree. */
if ((ys = yang_parse1(h, yang_dir, module, revision, ysp)) == NULL)
if ((ymod = yang_parse1(h, yang_dir, module, revision, ysp)) == NULL)
goto done;
/* Add top module name as dbspec-name */
clicon_dbspec_name_set(h, ys->ys_argument);
clicon_dbspec_name_set(h, ymod->ys_argument);
#ifdef YANG_TYPE_CACHE
/* Resolve all types */
yang_apply((yang_node*)ys, ys_resolve_type, NULL);
#endif
yang_apply((yang_node*)ysp, ys_resolve_type, NULL);
/* Step 2: Macro expansion of all grouping/uses pairs. Expansion needs marking */
if (yang_expand((yang_node*)ysp) < 0)
goto done;
yang_apply((yang_node*)ys, ys_flag_reset, (void*)YANG_FLAG_MARK);
yang_apply((yang_node*)ymod, ys_flag_reset, (void*)YANG_FLAG_MARK);
/* Step 4: Go through parse tree and populate it with cv types */
if (yang_apply((yang_node*)ysp, ys_populate, NULL) < 0)
goto done;
@ -1698,8 +1701,6 @@ yang_xpath_abs(yang_node *yn,
int nvec;
yang_node *ys = NULL;
yang_stmt *ymod;
yang_spec *yspec;
yang_stmt *yimport;
char *id;
char *prefix = NULL;
@ -1736,15 +1737,8 @@ yang_xpath_abs(yang_node *yn,
}
prefix[id-vec[1]] = '\0';
id++;
if ((yimport = ys_module_import(ymod, prefix)) == NULL){
clicon_err(OE_DB, 0, "Prefix %s not defined not found", prefix);
if ((ymod = yang_find_module_by_prefix((yang_stmt*)yn, prefix)) == NULL)
goto done;
}
yspec = ys_spec(ymod);
if ((ymod = yang_find((yang_node*)yspec, Y_MODULE, yimport->ys_argument)) == NULL){
clicon_err(OE_DB, 0, "Module referred to with prefix %s not found", prefix);
goto done;
}
}
ys = yang_xpath_vec((yang_node*)ymod, vec+1, nvec-1);
done:

View file

@ -179,7 +179,11 @@ clixon_yang_parsewrap(void)
<KEYWORD>yang-version { BEGIN(ARGUMENT); return K_YANG_VERSION; }
<KEYWORD>yin-element { BEGIN(ARGUMENT); return K_YIN_ELEMENT; }
<KEYWORD>. { return K_UNKNOWN; }
<KEYWORD>: { return *yytext; }
<KEYWORD>; { return *yytext; }
<KEYWORD>. { clixon_yang_parselval.string = strdup(yytext);
return CHAR;}
<ARGUMENT>; { BEGIN(KEYWORD); return *yytext; }
<ARGUMENT>\{ { BEGIN(KEYWORD); return *yytext; }

View file

@ -239,7 +239,9 @@ ystack_push(struct clicon_yang_yacc_arg *yy, yang_node *yn)
* Note: consumes 'argument' which assumes it is malloced and not freed by caller
*/
static yang_stmt *
ysp_add(struct clicon_yang_yacc_arg *yy, enum rfc_6020 keyword, char *argument)
ysp_add(struct clicon_yang_yacc_arg *yy,
enum rfc_6020 keyword,
char *argument)
{
struct ys_stack *ystack = yy->yy_stack;
yang_stmt *ys = NULL;
@ -453,7 +455,8 @@ body_stmts : body_stmts body_stmt { clicon_debug(2,"body-stmts -> body-stmts
| body_stmt { clicon_debug(2,"body-stmts -> body-stmt");}
;
body_stmt : feature_stmt { clicon_debug(2,"body-stmt -> feature-stmt");}
body_stmt : extension_stmt { clicon_debug(2,"body-stmt -> extension-stmt");}
| feature_stmt { clicon_debug(2,"body-stmt -> feature-stmt");}
| identity_stmt { clicon_debug(2,"body-stmt -> identity-stmt");}
| typedef_stmt { clicon_debug(2,"body-stmt -> typedef-stmt");}
| grouping_stmt { clicon_debug(2,"body-stmt -> grouping-stmt");}
@ -467,6 +470,7 @@ data_def_stmt : container_stmt { clicon_debug(2,"data-def-stmt -> containe
| leaf_list_stmt { clicon_debug(2,"data-def-stmt -> leaf-list-stmt");}
| list_stmt { clicon_debug(2,"data-def-stmt -> list-stmt");}
| choice_stmt { clicon_debug(2,"data-def-stmt -> choice-stmt");}
| anyxml_stmt { clicon_debug(2,"data-def-stmt -> anyxml-stmt");}
| uses_stmt { clicon_debug(2,"data-def-stmt -> uses-stmt");}
;
@ -633,16 +637,17 @@ short_case_stmt : container_stmt { clicon_debug(2,"short-case-substmt -> conta
| leaf_stmt { clicon_debug(2,"short-case-substmt -> leaf-stmt"); }
| leaf_list_stmt { clicon_debug(2,"short-case-substmt -> leaf-list-stmt"); }
| list_stmt { clicon_debug(2,"short-case-substmt -> list-stmt"); }
| anyxml_stmt { clicon_debug(2,"short-case-substmt -> anyxml-stmt");}
;
/* case */
case_stmt : K_CASE id_arg_str ';'
{ if (ysp_add(_yy, Y_CASE, $2) == NULL) _YYERROR("19");
{ if (ysp_add(_yy, Y_CASE, $2) == NULL) _YYERROR("22");
clicon_debug(2,"case-stmt -> CASE id-arg-str ;"); }
| K_CASE id_arg_str
{ if (ysp_add_push(_yy, Y_CASE, $2) == NULL) _YYERROR("20"); }
{ if (ysp_add_push(_yy, Y_CASE, $2) == NULL) _YYERROR("23"); }
'{' case_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("21");
{ if (ystack_pop(_yy) < 0) _YYERROR("24");
clicon_debug(2,"case-stmt -> CASE id-arg-str { case-substmts }"); }
;
@ -663,15 +668,42 @@ case_substmt : when_stmt { clicon_debug(2,"case-substmt -> when-stmt
;
/* anyxml */
anyxml_stmt : K_ANYXML id_arg_str ';'
{ if (ysp_add(_yy, Y_ANYXML, $2) == NULL) _YYERROR("25");
clicon_debug(2,"anyxml-stmt -> ANYXML id-arg-str ;"); }
| K_ANYXML id_arg_str
{ if (ysp_add_push(_yy, Y_ANYXML, $2) == NULL) _YYERROR("26"); }
'{' anyxml_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("27");
clicon_debug(2,"anyxml-stmt -> ANYXML id-arg-str { anyxml-substmts }"); }
;
anyxml_substmts : anyxml_substmts anyxml_substmt
{ clicon_debug(2,"anyxml-substmts -> anyxml-substmts anyxml-substmt"); }
| anyxml_substmt
{ clicon_debug(2,"anyxml-substmts -> anyxml-substmt"); }
;
anyxml_substmt : when_stmt { clicon_debug(2,"anyxml-substmt -> when-stmt"); }
| if_feature_stmt { clicon_debug(2,"anyxml-substmt -> if-feature-stmt"); }
| must_stmt { clicon_debug(2,"anyxml-substmt -> must-stmt"); }
| config_stmt { clicon_debug(2,"anyxml-substmt -> config-stmt"); }
| mandatory_stmt { clicon_debug(2,"anyxml-substmt -> mandatory-stmt"); }
| status_stmt { clicon_debug(2,"anyxml-substmt -> status-stmt"); }
| description_stmt { clicon_debug(2,"anyxml-substmt -> description-stmt"); }
| reference_stmt { clicon_debug(2,"anyxml-substmt -> reference-stmt"); }
| ustring ':' ustring ';' { clicon_debug(2,"anyxml-substmt -> anyxml extension"); }
;
/* uses */
uses_stmt : K_USES identifier_ref_arg_str ';'
{ if (ysp_add(_yy, Y_USES, $2) == NULL) _YYERROR("19");
{ if (ysp_add(_yy, Y_USES, $2) == NULL) _YYERROR("28");
clicon_debug(2,"uses-stmt -> USES id-arg-str ;"); }
| K_USES identifier_ref_arg_str
{ if (ysp_add_push(_yy, Y_USES, $2) == NULL) _YYERROR("20"); }
{ if (ysp_add_push(_yy, Y_USES, $2) == NULL) _YYERROR("29"); }
'{' uses_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("21");
{ if (ystack_pop(_yy) < 0) _YYERROR("30");
clicon_debug(2,"uses-stmt -> USES id-arg-str { uses-substmts }"); }
;
@ -694,12 +726,12 @@ uses_substmt : when_stmt { clicon_debug(2,"uses-substmt -> when-stmt
/* refine XXX need further refining */
refine_stmt : K_REFINE id_arg_str ';'
{ if (ysp_add(_yy, Y_REFINE, $2) == NULL) _YYERROR("21");
{ if (ysp_add(_yy, Y_REFINE, $2) == NULL) _YYERROR("31");
clicon_debug(2,"refine-stmt -> REFINE id-arg-str ;"); }
| K_REFINE id_arg_str
{ if (ysp_add_push(_yy, Y_REFINE, $2) == NULL) _YYERROR("22"); }
{ if (ysp_add_push(_yy, Y_REFINE, $2) == NULL) _YYERROR("32"); }
'{' refine_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("23");
{ if (ystack_pop(_yy) < 0) _YYERROR("33");
clicon_debug(2,"refine-stmt -> REFINE id-arg-str { refine-substmts }"); }
;
@ -720,9 +752,9 @@ refine_substmt : must_stmt { clicon_debug(2,"refine-substmt -> must-stmt");
uses_augment_stmt : augment_stmt;
augment_stmt : K_AUGMENT string
{ if (ysp_add_push(_yy, Y_AUGMENT, $2) == NULL) _YYERROR("22"); }
{ if (ysp_add_push(_yy, Y_AUGMENT, $2) == NULL) _YYERROR("34"); }
'{' augment_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("23");
{ if (ystack_pop(_yy) < 0) _YYERROR("35");
clicon_debug(2,"augment-stmt -> AUGMENT string { augment-substmts }"); }
;
@ -744,12 +776,12 @@ augment_substmt : when_stmt { clicon_debug(2,"augment-substmt -> when-s
/* when */
when_stmt : K_WHEN string ';'
{ if (ysp_add(_yy, Y_WHEN, $2) == NULL) _YYERROR("21");
{ if (ysp_add(_yy, Y_WHEN, $2) == NULL) _YYERROR("36");
clicon_debug(2,"when-stmt -> WHEN string ;"); }
| K_WHEN string
{ if (ysp_add_push(_yy, Y_WHEN, $2) == NULL) _YYERROR("22"); }
{ if (ysp_add_push(_yy, Y_WHEN, $2) == NULL) _YYERROR("37"); }
'{' when_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("23");
{ if (ystack_pop(_yy) < 0) _YYERROR("38");
clicon_debug(2,"when-stmt -> WHEN string { when-substmts }"); }
;
@ -766,12 +798,12 @@ when_substmt : description_stmt { clicon_debug(2,"when-substmt -> description-s
/* rpc */
rpc_stmt : K_RPC id_arg_str ';'
{ if (ysp_add(_yy, Y_RPC, $2) == NULL) _YYERROR("21");
{ if (ysp_add(_yy, Y_RPC, $2) == NULL) _YYERROR("39");
clicon_debug(2,"rpc-stmt -> RPC id-arg-str ;"); }
| K_RPC id_arg_str
{ if (ysp_add_push(_yy, Y_RPC, $2) == NULL) _YYERROR("22"); }
{ if (ysp_add_push(_yy, Y_RPC, $2) == NULL) _YYERROR("40"); }
'{' rpc_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("23");
{ if (ystack_pop(_yy) < 0) _YYERROR("41");
clicon_debug(2,"rpc-stmt -> RPC id-arg-str { rpc-substmts }"); }
;
@ -794,9 +826,9 @@ rpc_substmt : if_feature_stmt { clicon_debug(2,"rpc-substmt -> if-feature-stm
/* input */
input_stmt : K_INPUT
{ if (ysp_add_push(_yy, Y_INPUT, NULL) == NULL) _YYERROR("24"); }
{ if (ysp_add_push(_yy, Y_INPUT, NULL) == NULL) _YYERROR("42"); }
'{' input_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("25");
{ if (ystack_pop(_yy) < 0) _YYERROR("43");
clicon_debug(2,"input-stmt -> INPUT { input-substmts }"); }
;
@ -814,18 +846,18 @@ input_substmt : typedef_stmt { clicon_debug(2,"input-substmt -> typedef-
/* output */
output_stmt : K_OUTPUT /* XXX reuse input-substatements since they are same */
{ if (ysp_add_push(_yy, Y_OUTPUT, NULL) == NULL) _YYERROR("24"); }
{ if (ysp_add_push(_yy, Y_OUTPUT, NULL) == NULL) _YYERROR("44"); }
'{' input_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("25");
{ if (ystack_pop(_yy) < 0) _YYERROR("45");
clicon_debug(2,"output-stmt -> OUTPUT { input-substmts }"); }
;
/* Typedef */
typedef_stmt : K_TYPEDEF id_arg_str
{ if (ysp_add_push(_yy, Y_TYPEDEF, $2) == NULL) _YYERROR("24"); }
{ if (ysp_add_push(_yy, Y_TYPEDEF, $2) == NULL) _YYERROR("46"); }
'{' typedef_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("25");
{ if (ystack_pop(_yy) < 0) _YYERROR("47");
clicon_debug(2,"typedef-stmt -> TYPEDEF id-arg-str { typedef-substmts }"); }
;
@ -847,13 +879,13 @@ typedef_substmt : type_stmt { clicon_debug(2,"typedef-substmt -> type-s
/* Type */
type_stmt : K_TYPE identifier_ref_arg_str ';'
{ if (ysp_add(_yy, Y_TYPE, $2) == NULL) _YYERROR("26");
{ if (ysp_add(_yy, Y_TYPE, $2) == NULL) _YYERROR("48");
clicon_debug(2,"type-stmt -> TYPE identifier-ref-arg-str ;");}
| K_TYPE identifier_ref_arg_str
{ if (ysp_add_push(_yy, Y_TYPE, $2) == NULL) _YYERROR("27");
{ if (ysp_add_push(_yy, Y_TYPE, $2) == NULL) _YYERROR("49");
}
'{' type_body_stmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("28");
{ if (ystack_pop(_yy) < 0) _YYERROR("50");
clicon_debug(2,"type-stmt -> TYPE identifier-ref-arg-str { type-body-stmts }");}
;
@ -892,9 +924,9 @@ type_body_stmt/* numerical-restrictions */
/* Grouping */
grouping_stmt : K_GROUPING id_arg_str
{ if (ysp_add_push(_yy, Y_GROUPING, $2) == NULL) _YYERROR("29"); }
{ if (ysp_add_push(_yy, Y_GROUPING, $2) == NULL) _YYERROR("51"); }
'{' grouping_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("30");
{ if (ystack_pop(_yy) < 0) _YYERROR("52");
clicon_debug(2,"grouping-stmt -> GROUPING id-arg-str { grouping-substmts }"); }
;
@ -915,13 +947,13 @@ grouping_substmt : status_stmt { clicon_debug(2,"grouping-substmt -> st
/* length-stmt */
length_stmt : K_LENGTH string ';' /* XXX length-arg-str */
{ if (ysp_add(_yy, Y_LENGTH, $2) == NULL) _YYERROR("31");
{ if (ysp_add(_yy, Y_LENGTH, $2) == NULL) _YYERROR("53");
clicon_debug(2,"length-stmt -> LENGTH string ;"); }
| K_LENGTH string
{ if (ysp_add_push(_yy, Y_LENGTH, $2) == NULL) _YYERROR("32"); }
{ if (ysp_add_push(_yy, Y_LENGTH, $2) == NULL) _YYERROR("54"); }
'{' length_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("33");
{ if (ystack_pop(_yy) < 0) _YYERROR("55");
clicon_debug(2,"length-stmt -> LENGTH string { length-substmts }"); }
;
@ -940,13 +972,13 @@ length_substmt : error_message_stmt { clicon_debug(2,"length-substmt -> error-m
/* Pattern */
pattern_stmt : K_PATTERN string ';'
{ if (ysp_add(_yy, Y_PATTERN, $2) == NULL) _YYERROR("34");
{ if (ysp_add(_yy, Y_PATTERN, $2) == NULL) _YYERROR("56");
clicon_debug(2,"pattern-stmt -> PATTERN string ;"); }
| K_PATTERN string
{ if (ysp_add_push(_yy, Y_PATTERN, $2) == NULL) _YYERROR("35"); }
{ if (ysp_add_push(_yy, Y_PATTERN, $2) == NULL) _YYERROR("57"); }
'{' pattern_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("36");
{ if (ystack_pop(_yy) < 0) _YYERROR("58");
clicon_debug(2,"pattern-stmt -> PATTERN string { pattern-substmts }"); }
;
@ -962,18 +994,49 @@ pattern_substmt : reference_stmt { clicon_debug(2,"pattern-substmt -> refere
| { clicon_debug(2,"pattern-substmt -> "); }
;
/* Feature */
feature_stmt : K_FEATURE id_arg_str ';'
{ if (ysp_add(_yy, Y_FEATURE, $2) == NULL) _YYERROR("50");
clicon_debug(2,"feature-stmt -> FEATURE id-arg-str ;"); }
/* Extension */
extension_stmt: K_EXTENSION id_arg_str ';'
{ if (ysp_add(_yy, Y_EXTENSION, $2) == NULL) _YYERROR("59");
clicon_debug(2,"extenstion-stmt -> EXTENSION id-arg-str ;"); }
| K_EXTENSION id_arg_str
{ if (ysp_add_push(_yy, Y_EXTENSION, $2) == NULL) _YYERROR("60"); }
'{' extension_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("61");
clicon_debug(2,"extension-stmt -> FEATURE id-arg-str { extension-substmts }"); }
;
| K_FEATURE id_arg_str
{ if (ysp_add_push(_yy, Y_FEATURE, $2) == NULL) _YYERROR("51"); }
'{' feature_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("52");
clicon_debug(2,"feature-stmt -> FEATURE id-arg-str { feature-substmts }"); }
/* extension substmts */
extension_substmts : extension_substmts extension_substmt
{ clicon_debug(2,"extension-substmts -> extension-substmts extension-substmt"); }
| extension_substmt
{ clicon_debug(2,"extension-substmts -> extension-substmt"); }
;
extension_substmt : argument_stmt { clicon_debug(2,"extension-substmt -> argument-stmt"); }
| status_stmt { clicon_debug(2,"extension-substmt -> status-stmt"); }
| description_stmt { clicon_debug(2,"extension-substmt -> description-stmt"); }
| reference_stmt { clicon_debug(2,"extension-substmt -> reference-stmt"); }
| unknown_stmt { clicon_debug(2,"extension-substmt -> unknown-stmt");}
| { clicon_debug(2,"extension-substmt -> "); }
;
argument_stmt : K_ARGUMENT id_arg_str ';'
| K_ARGUMENT id_arg_str '{' '}'
;
/* Feature */
feature_stmt : K_FEATURE id_arg_str ';'
{ if (ysp_add(_yy, Y_FEATURE, $2) == NULL) _YYERROR("62");
clicon_debug(2,"feature-stmt -> FEATURE id-arg-str ;"); }
| K_FEATURE id_arg_str
{ if (ysp_add_push(_yy, Y_FEATURE, $2) == NULL) _YYERROR("63"); }
'{' feature_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("64");
clicon_debug(2,"feature-stmt -> FEATURE id-arg-str { feature-substmts }"); }
;
/* feature substmts */
feature_substmts : feature_substmts feature_substmt
{ clicon_debug(2,"feature-substmts -> feature-substmts feature-substmt"); }
| feature_substmt
@ -990,13 +1053,13 @@ feature_substmt : if_feature_stmt { clicon_debug(2,"feature-substmt -> if-fea
/* Identity */
identity_stmt : K_IDENTITY string ';' /* XXX identifier-arg-str */
{ if (ysp_add(_yy, Y_IDENTITY, $2) == NULL) _YYERROR("53");
{ if (ysp_add(_yy, Y_IDENTITY, $2) == NULL) _YYERROR("65");
clicon_debug(2,"identity-stmt -> IDENTITY string ;"); }
| K_IDENTITY string
{ if (ysp_add_push(_yy, Y_IDENTITY, $2) == NULL) _YYERROR("54"); }
{ if (ysp_add_push(_yy, Y_IDENTITY, $2) == NULL) _YYERROR("66"); }
'{' identity_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("55");
{ if (ystack_pop(_yy) < 0) _YYERROR("67");
clicon_debug(2,"identity-stmt -> IDENTITY string { identity-substmts }"); }
;
@ -1016,13 +1079,13 @@ identity_substmt : base_stmt { clicon_debug(2,"identity-substmt -> base-
/* range-stmt */
range_stmt : K_RANGE string ';' /* XXX range-arg-str */
{ if (ysp_add(_yy, Y_RANGE, $2) == NULL) _YYERROR("56");
{ if (ysp_add(_yy, Y_RANGE, $2) == NULL) _YYERROR("68");
clicon_debug(2,"range-stmt -> RANGE string ;"); }
| K_RANGE string
{ if (ysp_add_push(_yy, Y_RANGE, $2) == NULL) _YYERROR("57"); }
{ if (ysp_add_push(_yy, Y_RANGE, $2) == NULL) _YYERROR("69"); }
'{' range_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("58");
{ if (ystack_pop(_yy) < 0) _YYERROR("70");
clicon_debug(2,"range-stmt -> RANGE string { range-substmts }"); }
;
@ -1041,13 +1104,12 @@ range_substmt : error_message_stmt { clicon_debug(2,"range-substmt -> error-me
/* enum-stmt */
enum_stmt : K_ENUM string ';'
{ if (ysp_add(_yy, Y_ENUM, $2) == NULL) _YYERROR("59");
{ if (ysp_add(_yy, Y_ENUM, $2) == NULL) _YYERROR("71");
clicon_debug(2,"enum-stmt -> ENUM string ;"); }
| K_ENUM string
{ if (ysp_add_push(_yy, Y_ENUM, $2) == NULL) _YYERROR("60"); }
{ if (ysp_add_push(_yy, Y_ENUM, $2) == NULL) _YYERROR("72"); }
'{' enum_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("61");
{ if (ystack_pop(_yy) < 0) _YYERROR("73");
clicon_debug(2,"enum-stmt -> ENUM string { enum-substmts }"); }
;
@ -1067,13 +1129,12 @@ enum_substmt : value_stmt { clicon_debug(2,"enum-substmt -> value-stm
/* bit-stmt */
bit_stmt : K_BIT string ';'
{ if (ysp_add(_yy, Y_BIT, $2) == NULL) _YYERROR("62");
{ if (ysp_add(_yy, Y_BIT, $2) == NULL) _YYERROR("74");
clicon_debug(2,"bit-stmt -> BIT string ;"); }
| K_BIT string
{ if (ysp_add_push(_yy, Y_BIT, $2) == NULL) _YYERROR("63"); }
{ if (ysp_add_push(_yy, Y_BIT, $2) == NULL) _YYERROR("75"); }
'{' bit_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("64");
{ if (ystack_pop(_yy) < 0) _YYERROR("76");
clicon_debug(2,"bit-stmt -> BIT string { bit-substmts }"); }
;
@ -1092,13 +1153,13 @@ bit_substmt : position_stmt { clicon_debug(2,"bit-substmt -> positition
/* mus-stmt */
must_stmt : K_MUST string ';'
{ if (ysp_add(_yy, Y_MUST, $2) == NULL) _YYERROR("65");
{ if (ysp_add(_yy, Y_MUST, $2) == NULL) _YYERROR("77");
clicon_debug(2,"must-stmt -> MUST string ;"); }
| K_MUST string
{ if (ysp_add_push(_yy, Y_MUST, $2) == NULL) _YYERROR("66"); }
{ if (ysp_add_push(_yy, Y_MUST, $2) == NULL) _YYERROR("78"); }
'{' must_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("67");
{ if (ystack_pop(_yy) < 0) _YYERROR("79");
clicon_debug(2,"must-stmt -> MUST string { must-substmts }"); }
;
@ -1116,13 +1177,13 @@ must_substmt : error_message_stmt { clicon_debug(2,"must-substmt -> error-mes
/* error-message-stmt */
error_message_stmt : K_ERROR_MESSAGE string ';'
{ if (ysp_add(_yy, Y_ERROR_MESSAGE, $2) == NULL) _YYERROR("68"); }
{ if (ysp_add(_yy, Y_ERROR_MESSAGE, $2) == NULL) _YYERROR("80"); }
/* import */
import_stmt : K_IMPORT id_arg_str
{ if (ysp_add_push(_yy, Y_IMPORT, $2) == NULL) _YYERROR("69"); }
{ if (ysp_add_push(_yy, Y_IMPORT, $2) == NULL) _YYERROR("81"); }
'{' import_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("70");
{ if (ystack_pop(_yy) < 0) _YYERROR("82");
clicon_debug(2,"import-stmt -> IMPORT id-arg-str { import-substmts }");}
;
@ -1139,144 +1200,144 @@ import_substmt : prefix_stmt { clicon_debug(2,"import-stmt -> prefix-stmt"); }
/* Simple statements */
yang_version_stmt : K_YANG_VERSION string ';' /* XXX yang-version-arg-str */
{ if (ysp_add(_yy, Y_YANG_VERSION, $2) == NULL) _YYERROR("71");
{ if (ysp_add(_yy, Y_YANG_VERSION, $2) == NULL) _YYERROR("83");
clicon_debug(2,"yang-version-stmt -> YANG-VERSION string"); }
;
fraction_digits_stmt : K_FRACTION_DIGITS string ';' /* XXX: fraction-digits-arg-str */
{ if (ysp_add(_yy, Y_FRACTION_DIGITS, $2) == NULL) _YYERROR("72");
{ if (ysp_add(_yy, Y_FRACTION_DIGITS, $2) == NULL) _YYERROR("84");
clicon_debug(2,"fraction-digits-stmt -> FRACTION-DIGITS string"); }
;
if_feature_stmt : K_IF_FEATURE identifier_ref_arg_str ';'
{ if (ysp_add(_yy, Y_IF_FEATURE, $2) == NULL) _YYERROR("73");
{ if (ysp_add(_yy, Y_IF_FEATURE, $2) == NULL) _YYERROR("85");
clicon_debug(2,"if-feature-stmt -> IF-FEATURE identifier-ref-arg-str"); }
;
value_stmt : K_VALUE integer_value ';'
{ if (ysp_add(_yy, Y_VALUE, $2) == NULL) _YYERROR("74");
{ if (ysp_add(_yy, Y_VALUE, $2) == NULL) _YYERROR("86");
clicon_debug(2,"value-stmt -> VALUE integer-value"); }
;
position_stmt : K_POSITION integer_value ';'
{ if (ysp_add(_yy, Y_POSITION, $2) == NULL) _YYERROR("75");
{ if (ysp_add(_yy, Y_POSITION, $2) == NULL) _YYERROR("87");
clicon_debug(2,"position-stmt -> POSITION integer-value"); }
;
status_stmt : K_STATUS string ';' /* XXX: status-arg-str */
{ if (ysp_add(_yy, Y_STATUS, $2) == NULL) _YYERROR("76");
{ if (ysp_add(_yy, Y_STATUS, $2) == NULL) _YYERROR("88");
clicon_debug(2,"status-stmt -> STATUS string"); }
;
config_stmt : K_CONFIG config_arg_str ';'
{ if (ysp_add(_yy, Y_CONFIG, $2) == NULL) _YYERROR("77");
{ if (ysp_add(_yy, Y_CONFIG, $2) == NULL) _YYERROR("89");
clicon_debug(2,"config-stmt -> CONFIG config-arg-str"); }
;
base_stmt : K_BASE identifier_ref_arg_str ';'
{ if (ysp_add(_yy, Y_BASE, $2)== NULL) _YYERROR("78");
{ if (ysp_add(_yy, Y_BASE, $2)== NULL) _YYERROR("90");
clicon_debug(2,"base-stmt -> BASE identifier-ref-arg-str"); }
;
path_stmt : K_PATH string ';' /* XXX: path-arg-str */
{ if (ysp_add(_yy, Y_PATH, $2)== NULL) _YYERROR("79");
{ if (ysp_add(_yy, Y_PATH, $2)== NULL) _YYERROR("91");
clicon_debug(2,"path-stmt -> PATH string"); }
;
require_instance_stmt : K_REQUIRE_INSTANCE string ';' /* XXX: require-instance-arg-str */
{ if (ysp_add(_yy, Y_REQUIRE_INSTANCE, $2)== NULL) _YYERROR("90");
{ if (ysp_add(_yy, Y_REQUIRE_INSTANCE, $2)== NULL) _YYERROR("92");
clicon_debug(2,"require-instance-stmt -> REQUIRE-INSTANCE string"); }
;
units_stmt : K_UNITS string ';'
{ if (ysp_add(_yy, Y_UNITS, $2)== NULL) _YYERROR("91");
{ if (ysp_add(_yy, Y_UNITS, $2)== NULL) _YYERROR("93");
clicon_debug(2,"units-stmt -> UNITS string"); }
;
default_stmt : K_DEFAULT string ';'
{ if (ysp_add(_yy, Y_DEFAULT, $2)== NULL) _YYERROR("92");
{ if (ysp_add(_yy, Y_DEFAULT, $2)== NULL) _YYERROR("94");
clicon_debug(2,"default-stmt -> DEFAULT string"); }
;
contact_stmt : K_CONTACT string ';'
{ if (ysp_add(_yy, Y_CONTACT, $2)== NULL) _YYERROR("93");
{ if (ysp_add(_yy, Y_CONTACT, $2)== NULL) _YYERROR("95");
clicon_debug(2,"contact-stmt -> CONTACT string"); }
;
revision_date_stmt : K_REVISION_DATE string ';' /* XXX date-arg-str */
{ if (ysp_add(_yy, Y_REVISION_DATE, $2) == NULL) _YYERROR("94");
{ if (ysp_add(_yy, Y_REVISION_DATE, $2) == NULL) _YYERROR("96");
clicon_debug(2,"revision-date-stmt -> date;"); }
;
include_stmt : K_INCLUDE id_arg_str ';'
{ if (ysp_add(_yy, Y_INCLUDE, $2)== NULL) _YYERROR("95");
{ if (ysp_add(_yy, Y_INCLUDE, $2)== NULL) _YYERROR("97");
clicon_debug(2,"include-stmt -> id-arg-str"); }
| K_INCLUDE id_arg_str '{' revision_date_stmt '}'
{ if (ysp_add(_yy, Y_INCLUDE, $2)== NULL) _YYERROR("96");
{ if (ysp_add(_yy, Y_INCLUDE, $2)== NULL) _YYERROR("98");
clicon_debug(2,"include-stmt -> id-arg-str { revision-date-stmt }"); }
;
namespace_stmt : K_NAMESPACE string ';' /* XXX uri-str */
{ if (ysp_add(_yy, Y_NAMESPACE, $2)== NULL) _YYERROR("97");
{ if (ysp_add(_yy, Y_NAMESPACE, $2)== NULL) _YYERROR("99");
clicon_debug(2,"namespace-stmt -> NAMESPACE string"); }
;
prefix_stmt : K_PREFIX string ';' /* XXX prefix-arg-str */
{ if (ysp_add(_yy, Y_PREFIX, $2)== NULL) _YYERROR("98");
{ if (ysp_add(_yy, Y_PREFIX, $2)== NULL) _YYERROR("100");
clicon_debug(2,"prefix-stmt -> PREFIX string ;");}
;
description_stmt: K_DESCRIPTION string ';'
{ if (ysp_add(_yy, Y_DESCRIPTION, $2)== NULL) _YYERROR("99");
{ if (ysp_add(_yy, Y_DESCRIPTION, $2)== NULL) _YYERROR("101");
clicon_debug(2,"description-stmt -> DESCRIPTION string ;");}
;
organization_stmt: K_ORGANIZATION string ';'
{ if (ysp_add(_yy, Y_ORGANIZATION, $2)== NULL) _YYERROR("100");
{ if (ysp_add(_yy, Y_ORGANIZATION, $2)== NULL) _YYERROR("102");
clicon_debug(2,"organization-stmt -> ORGANIZATION string ;");}
;
min_elements_stmt: K_MIN_ELEMENTS integer_value ';'
{ if (ysp_add(_yy, Y_MIN_ELEMENTS, $2)== NULL) _YYERROR("101");
{ if (ysp_add(_yy, Y_MIN_ELEMENTS, $2)== NULL) _YYERROR("103");
clicon_debug(2,"min-elements-stmt -> MIN-ELEMENTS integer ;");}
;
max_elements_stmt: K_MAX_ELEMENTS integer_value ';'
{ if (ysp_add(_yy, Y_MAX_ELEMENTS, $2)== NULL) _YYERROR("101");
{ if (ysp_add(_yy, Y_MAX_ELEMENTS, $2)== NULL) _YYERROR("104");
clicon_debug(2,"max-elements-stmt -> MIN-ELEMENTS integer ;");}
;
reference_stmt: K_REFERENCE string ';'
{ if (ysp_add(_yy, Y_REFERENCE, $2)== NULL) _YYERROR("101");
{ if (ysp_add(_yy, Y_REFERENCE, $2)== NULL) _YYERROR("105");
clicon_debug(2,"reference-stmt -> REFERENCE string ;");}
;
mandatory_stmt: K_MANDATORY string ';'
{ yang_stmt *ys;
if ((ys = ysp_add(_yy, Y_MANDATORY, $2))== NULL) _YYERROR("102");
if ((ys = ysp_add(_yy, Y_MANDATORY, $2))== NULL) _YYERROR("106");
clicon_debug(2,"mandatory-stmt -> MANDATORY mandatory-arg-str ;");}
;
presence_stmt: K_PRESENCE string ';'
{ yang_stmt *ys;
if ((ys = ysp_add(_yy, Y_PRESENCE, $2))== NULL) _YYERROR("102");
if ((ys = ysp_add(_yy, Y_PRESENCE, $2))== NULL) _YYERROR("107");
clicon_debug(2,"presence-stmt -> PRESENCE string ;");}
;
ordered_by_stmt: K_ORDERED_BY string ';'
{ yang_stmt *ys;
if ((ys = ysp_add(_yy, Y_ORDERED_BY, $2))== NULL) _YYERROR("102");
if ((ys = ysp_add(_yy, Y_ORDERED_BY, $2))== NULL) _YYERROR("108");
clicon_debug(2,"ordered-by-stmt -> ORDERED-BY ordered-by-arg ;");}
;
key_stmt : K_KEY id_arg_str ';' /* XXX key_arg_str */
{ if (ysp_add(_yy, Y_KEY, $2)== NULL) _YYERROR("103");
{ if (ysp_add(_yy, Y_KEY, $2)== NULL) _YYERROR("109");
clicon_debug(2,"key-stmt -> KEY id-arg-str ;");}
;
unique_stmt : K_UNIQUE id_arg_str ';' /* XXX key_arg_str */
{ if (ysp_add(_yy, Y_UNIQUE, $2)== NULL) _YYERROR("104");
{ if (ysp_add(_yy, Y_UNIQUE, $2)== NULL) _YYERROR("110");
clicon_debug(2,"key-stmt -> KEY id-arg-str ;");}
;
@ -1287,10 +1348,10 @@ integer_value : string { $$=$1; }
;
identifier_ref_arg_str : string
{ if (($$=prefix_id_join(NULL, $1)) == NULL) _YYERROR("105");
{ if (($$=prefix_id_join(NULL, $1)) == NULL) _YYERROR("111");
clicon_debug(2,"identifier-ref-arg-str -> string"); }
| string ':' string
{ if (($$=prefix_id_join($1, $3)) == NULL) _YYERROR("106");
{ if (($$=prefix_id_join($1, $3)) == NULL) _YYERROR("112");
clicon_debug(2,"identifier-ref-arg-str -> prefix : string"); }
;

View file

@ -71,19 +71,10 @@
/*
* Local types and variables
*/
/* Struct used to map between int and strings. Used for:
* - mapping yang types/typedefs (strings) and cligen types (ints).
* - mapping yang keywords (strings) and enum (clicon)
* (same struct in clicon_yang.c)
*/
struct map_str2int{
char *ms_str; /* string as in 4.2.4 in RFC 6020 */
int ms_int;
};
/* Mapping between yang types <--> cligen types
Note, first match used wne translating from cv to yang --> order is significant */
static const struct map_str2int ytmap[] = {
static const map_str2int ytmap[] = {
{"int32", CGV_INT32}, /* NOTE, first match on right is significant, dont move */
{"string", CGV_STRING}, /* NOTE, first match on right is significant, dont move */
{"string", CGV_REST}, /* For cv -> yang translation of rest */
@ -105,9 +96,24 @@ static const struct map_str2int ytmap[] = {
{"uint32", CGV_UINT32},
{"uint64", CGV_UINT64},
{"union", CGV_REST}, /* Is replaced by actual type */
{NULL, -1}
{NULL, -1}
};
/* return 1 if built-in, 0 if not */
static int
yang_builtin(char *type)
{
if (clicon_str2int(ytmap, type) != -1)
return 1;
#if 0
const struct map_str2int *yt;
for (yt = &ytmap[0]; yt->ms_str; yt++)
if (strcmp(yt->ms_str, type) == 0)
return 1;
#endif
return 0;
}
int
yang_type_cache_set(yang_type_cache **ycache0,
yang_stmt *resolved,
@ -174,7 +180,8 @@ yang_type_cache_get(yang_type_cache *ycache,
}
int
yang_type_cache_cp(yang_type_cache **ycnew, yang_type_cache *ycold)
yang_type_cache_cp(yang_type_cache **ycnew,
yang_type_cache *ycold)
{
int retval = -1;
int options;
@ -207,7 +214,8 @@ yang_type_cache_free(yang_type_cache *ycache)
/*! Resolve types: populate type caches */
int
ys_resolve_type(yang_stmt *ys, void *arg)
ys_resolve_type(yang_stmt *ys,
void *arg)
{
int retval = -1;
int options = 0x0;
@ -219,8 +227,9 @@ ys_resolve_type(yang_stmt *ys, void *arg)
if (ys->ys_keyword != Y_TYPE)
return 0;
yang_type_resolve((yang_stmt*)ys->ys_parent, ys, &resolved,
&options, &mincv, &maxcv, &pattern, &fraction);
if (yang_type_resolve((yang_stmt*)ys->ys_parent, ys, &resolved,
&options, &mincv, &maxcv, &pattern, &fraction) < 0)
goto done;
if (yang_type_cache_set(&ys->ys_typecache,
resolved, options, mincv, maxcv, pattern, fraction) < 0)
goto done;
@ -229,20 +238,6 @@ ys_resolve_type(yang_stmt *ys, void *arg)
return retval;
}
/* return 1 if built-in, 0 if not */
static int
yang_builtin(char *type)
{
const struct map_str2int *yt;
/* built-in types */
for (yt = &ytmap[0]; yt->ms_str; yt++)
if (strcmp(yt->ms_str, type) == 0)
return 1;
return 0;
}
/*! Translate from a yang type to a cligen variable type
*
* Currently many built-in types from RFC6020 and some RFC6991 types.
@ -252,17 +247,17 @@ yang_builtin(char *type)
* Return 0 if no match but set cv_type to CGV_ERR
*/
int
yang2cv_type(char *ytype, enum cv_type *cv_type)
yang2cv_type(char *ytype,
enum cv_type *cv_type)
{
const struct map_str2int *yt;
int ret;
*cv_type = CGV_ERR;
/* built-in types */
for (yt = &ytmap[0]; yt->ms_str; yt++)
if (strcmp(yt->ms_str, ytype) == 0){
*cv_type = yt->ms_int;
return 0;
}
if ((ret = clicon_str2int(ytmap, ytype)) != -1){
*cv_type = ret;
return 0;
}
/* special derived types */
if (strcmp("ipv4-address", ytype) == 0){ /* RFC6991 */
*cv_type = CGV_IPV4ADDR;
@ -300,14 +295,13 @@ yang2cv_type(char *ytype, enum cv_type *cv_type)
char *
cv2yang_type(enum cv_type cv_type)
{
const struct map_str2int *yt;
char *ytype;
const char *str;
ytype = "empty";
/* built-in types */
for (yt = &ytmap[0]; yt->ms_str; yt++)
if (yt->ms_int == cv_type)
return yt->ms_str;
if ((str = clicon_int2str(ytmap, cv_type)) != NULL)
return (char*)str;
/* special derived types */
if (cv_type == CGV_IPV4ADDR) /* RFC6991 */
@ -343,7 +337,9 @@ cv2yang_type(enum cv_type cv_type)
* @param[out] cvtype
*/
int
clicon_type2cv(char *origtype, char *restype, enum cv_type *cvtype)
clicon_type2cv(char *origtype,
char *restype,
enum cv_type *cvtype)
{
int retval = -1;
@ -710,7 +706,7 @@ ys_typedef_up(yang_stmt *ys)
This is a sanity check of base identity of identity-ref and for identity
statements.
Return true if node is identityref and is derived from identity_name
Return true if node is identityref and is derived from identity_name
The derived-from() function returns true if the (first) node (in
document order in the argument "nodes") is a node of type identityref,
and its value is an identity that is derived from the identity
@ -728,16 +724,14 @@ Return true if node is identityref and is derived from identity_name
vad är det denna function ska göra? Svar: 1
*/
yang_stmt *
yang_find_identity(yang_stmt *ys, char *identity)
yang_find_identity(yang_stmt *ys,
char *identity)
{
char *id;
char *prefix = NULL;
yang_stmt *yimport;
yang_spec *yspec;
yang_stmt *ymodule;
yang_stmt *yid = NULL;
yang_node *yn;
yang_stmt *ymod;
if ((id = strchr(identity, ':')) == NULL)
id = identity;
@ -748,12 +742,8 @@ yang_find_identity(yang_stmt *ys, char *identity)
}
/* No, now check if identityref is derived from base */
if (prefix){ /* Go to top and find import that matches */
ymod = ys_module(ys);
if ((yimport = ys_module_import(ymod, prefix)) == NULL)
if ((ymodule = yang_find_module_by_prefix(ys, prefix)) == NULL)
goto done;
yspec = ys_spec(ys);
if ((ymodule = yang_find((yang_node*)yspec, Y_MODULE, yimport->ys_argument)) == NULL)
goto done; /* unresolved */
yid = yang_find((yang_node*)ymodule, Y_IDENTITY, id);
}
else{
@ -845,12 +835,10 @@ yang_type_resolve(yang_stmt *ys,
yang_stmt *ylength;
yang_stmt *ypattern;
yang_stmt *yfraction;
yang_stmt *yimport;
char *type;
char *prefix = NULL;
int retval = -1;
yang_node *yn;
yang_spec *yspec;
yang_stmt *ymod;
if (options)
@ -879,14 +867,8 @@ yang_type_resolve(yang_stmt *ys,
/* Not basic type. Now check if prefix which means we look in other module */
if (prefix){ /* Go to top and find import that matches */
ymod = ys_module(ys);
if ((yimport = ys_module_import(ymod, prefix)) == NULL){
clicon_err(OE_DB, 0, "Prefix %s not defined not found", prefix);
if ((ymod = yang_find_module_by_prefix(ys, prefix)) == NULL)
goto done;
}
yspec = ys_spec(ys);
if ((ymod = yang_find((yang_node*)yspec, Y_MODULE, yimport->ys_argument)) == NULL)
goto ok; /* unresolved */
if ((rytypedef = yang_find((yang_node*)ymod, Y_TYPEDEF, type)) == NULL)
goto ok; /* unresolved */
}

View file

@ -42,7 +42,7 @@ expectfn(){
# echo "expect:\"$expect\""
# echo "match:\"$match\""
if [ -z "$match" ]; then
err $expect "$ret"
err "$expect" "$ret"
fi
if [ -n "$expect2" ]; then
match=`echo "$ret" | grep -EZo "$expect2"`

View file

@ -86,6 +86,12 @@ expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><get-config><source><candidate/
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf edit state operation should fail"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><edit-config><target><candidate/></target><config><interfaces-state><interface><name>eth1</name><type>eth</type></interface></interfaces-state></config></edit-config></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-tag>invalid-value</error-tag>"
new "netconf get state operation"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><get><filter type=\"xpath\" select=\"/interfaces-state\"/></get></rpc>]]>]]>" "^<rpc-reply><data/></rpc-reply>]]>]]>$"
new "netconf lock/unlock"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><lock><target><candidate/></target></lock></rpc>]]>]]><rpc><unlock><target><candidate/></target></unlock></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]><rpc-reply><ok/></rpc-reply>]]>]]>$"

View file

@ -7,6 +7,7 @@
# For memcheck
# clixon_netconf="valgrind --leak-check=full --show-leak-kinds=all clixon_netconf"
clixon_netconf=clixon_netconf
clixon_cli=clixon_cli
cat <<EOF > /tmp/test.yang
module ietf-ip{
@ -40,6 +41,12 @@ module ietf-ip{
}
}
}
container state {
config false;
leaf-list op {
type string;
}
}
}
EOF
@ -74,6 +81,19 @@ expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><get-config><sourc
new "netconf get leaf-list path"
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/x/f[e=hej]\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><config><x><f><e>hej</e><e>hopp</e></f></x></config></data></rpc-reply>]]>]]>$"
new "netconf get (state data XXX should be some)"
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><get><filter type=\"xpath\" select=\"/\"/></get></rpc>]]>]]>" "^<rpc-reply><data><config><x><y><a>1</a><b>2</b><c>5</c></y><d/></x></config></data></rpc-reply>]]>]]>$"
new "cli set leaf-list"
expectfn "$clixon_cli -1f $clixon_cf -y /tmp/test set x f e foo" ""
new "cli show leaf-list"
expectfn "$clixon_cli -1f $clixon_cf -y /tmp/test show xpath /x/f/e" "<e>foo</e>"
new "netconf set state data (not allowed)"
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><edit-config><target><candidate/></target><config><state><op>42</op></state></config></edit-config></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-tag>invalid-value"
new "Kill backend"
# Check if still alive
pid=`pgrep clixon_backend`

View file

@ -1,12 +1,12 @@
#!/bin/bash
# Test5: datastore
# Test5: datastore tests.
# Just run a binary direct to datastore. No clixon.
# include err() and new() functions
. ./lib.sh
datastore=datastore_client
cat <<EOF > /tmp/ietf-ip.yang
module ietf-ip{
container x {
@ -54,7 +54,7 @@ run(){
rm -rf $dir/*
conf="-d candidate -b $dir -p ../datastore/$name/$name.so -y /tmp -m ietf-ip"
# echo "conf:$conf"
echo "conf:$conf"
new "datastore $name init"
expectfn "$datastore $conf init" ""
@ -139,8 +139,18 @@ run(){
new "datastore $name create leaf"
expectfn "$datastore $conf put create <config><x><y><a>1</a><b>3</b><c>newentry</c></y></x></config>"
new "datastore other db init"
expectfn "$datastore -d kalle -b $dir -p ../datastore/$name/$name.so -y /tmp -m ietf-ip init"
new "datastore other db copy"
expectfn "$datastore $conf copy kalle" ""
diff $dir/kalle_db $dir/candidate_db
new "datastore lock"
expectfn "$datastore $conf lock 756" ""
#leaf-list
rm -rf $dir
}

64
test/test6.sh Executable file
View file

@ -0,0 +1,64 @@
#!/bin/bash
# Test6: Yang specifics: rpc and state info
# 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
clixon_cli=clixon_cli
cat <<EOF > /tmp/rpc.yang
module ietf-ip{
rpc fib-route {
input {
leaf name {
type string;
mandatory "true";
}
leaf destination-address {
type string;
}
}
output {
container route {
leaf address{
type string;
}
leaf address{
type string;
}
}
}
}
}
EOF
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $clixon_cf -y /tmp/rpc
if [ $? -ne 0 ]; then
err
fi
new "start backend"
# start new backend
sudo clixon_backend -If $clixon_cf -y /tmp/rpc
if [ $? -ne 0 ]; then
err
fi
new "netconf rpc (notyet)"
#expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/rpc" "<rpc><fib-route><name></name></fib-route></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "Kill backend"
# Check if still alive
pid=`pgrep clixon_backend`
if [ -z "$pid" ]; then
err "backend already dead"
fi
# kill backend
sudo clixon_backend -zf $clixon_cf
if [ $? -ne 0 ]; then
err "kill backend"
fi

View file

@ -0,0 +1,179 @@
module clixon-config {
prefix cc;
organization
"Clicon / Clixon";
contact
"Olof Hagsand <olof@hagsand.se>";
description
"Clixon configuration file
***** 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 *****";
revision 2017-07-02 {
description
"Initial revision";
}
leaf CLICON_CONFIGFILE{
type string;
default "sysconfdir/$APPNAME.conf";
description "Location of configuration-file for default values (this file)";
}
leaf CLICON_YANG_DIR {
type string;
default "prefix/share/$APPNAME/yang";
description "Location of YANG module and submodule files. Only if CLICON_DBSPEC_TYPE is YANG";
}
leaf CLICON_YANG_MODULE_MAIN {
type string;
default "clicon";
description "Option used to construct initial yang file:
<module>[@<revision>]
This option is only relevant if CLICON_DBSPEC_TYPE is YANG";
}
leaf CLICON_YANG_MODULE_REVISION {
type string;
description "Option used to construct initial yang file:
<module>[@<revision>]";
}
leaf CLICON_BACKEND_DIR {
type string;
default "libdir/$APPNAME/backend";
description "Location of backend .so plugins";
}
leaf CLICON_NETCONF_DIR {
type string;
default "libdir/$APPNAME/netconf";
description "Location of netconf (frontend) .so plugins";
}
leaf CLICON_RESTCONF_DIR {
type string;
default "libdir/$APPNAME/restconf";
description "Location of restconf (frontend) .so plugins";
}
leaf CLICON_CLI_DIR {
type string;
default "libdir/$APPNAME/cli";
description "Location of cli frontend .so plugins";
}
leaf CLICON_CLISPEC_DIR {
type string;
default "libdir/$APPNAME/clispec";
description "Location of frontend .cli cligen spec files";
}
leaf CLICON_USE_STARTUP_CONFIG {
type int32;
default 0;
description "Enabled uses \"startup\" configuration on boot";
}
leaf CLICON_SOCK_FAMILY {
type string;
default "UNIX";
description "Address family for communicating with clixon_backend (UNIX|IPv4|IPv6)";
}
leaf CLICON_SOCK {
type string;
default "localstatedir/$APPNAME/$APPNAME.sock";
description "If family above is AF_UNIX: Unix socket for communicating with
clixon_backend. If family above is AF_INET: IPv4 address";
}
leaf CLICON_SOCK_PORT {
type int32;
default 4535;
description "Inet socket port for communicating with clixon_backend (only IPv4|IPv6)";
}
leaf CLICON_BACKEND_PIDFILE {
type string;
default "localstatedir/$APPNAME/$APPNAME.pidfile";
description "Process-id file";
}
leaf CLICON_SOCK_GROUP {
type string;
default "clicon";
description "Group membership to access clixon_backend unix socket";
}
leaf CLICON_AUTOCOMMIT {
type int32;
default 0;
description "Set if all configuration changes are committed directly,
commit command unnecessary";
}
leaf CLICON_MASTER_PLUGIN {
type string;
default "master";
description "Name of master plugin (both frontend and backend).
Master plugin has special callbacks for frontends.
See clicon user manual for more info.";
}
leaf CLICON_CLI_MODE {
type string;
default "base";
description "Startup CLI mode. This should match the CLICON_MODE in your startup clispec file";
}
leaf CLICON_CLI_GENMODEL {
type int32;
default 1;
description "Generate code for CLI completion of existing db symbols.
Add name=\"myspec\" in datamodel spec and reference as @myspec";
}
leaf CLICON_CLI_GENMODEL_COMPLETION {
type int32;
default 0;
description "Generate code for CLI completion of existing db symbols";
}
leaf CLICON_CLI_GENMODEL_TYPE {
type string;
default "VARS";
description "How to generate and show CLI syntax: VARS|ALL";
}
leaf CLICON_XMLDB_DIR {
type string;
default "localstatedir/$APPNAME";
description "Directory where \"running\", \"candidate\" and \"startup\" are placed";
}
leaf CLICON_XMLDB_PLUGIN {
type string;
default "libdir/xmldb/text.so";
description "XMLDB datastore plugin filename (see datastore/ and clixon_xml_db.[ch])";
}
leaf CLICON_CLI_VARONLY {
type int32;
default 1;
description "Dont include keys in cvec in cli vars callbacks, ie a & k in 'a <b> k <c>' ignored";
}
leaf CLICON_RESTCONF_PATH {
type string;
default "/www-data/fastcgi_restconf.sock";
description "FastCGI unix socket. Should be specified in webserver
Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock;";
}
}

View file

@ -0,0 +1,926 @@
module ietf-netconf {
// the namespace for NETCONF XML definitions is unchanged
// from RFC 4741, which this document replaces
namespace "urn:ietf:params:xml:ns:netconf:base:1.0";
prefix nc;
import ietf-inet-types {
prefix inet;
}
organization
"IETF NETCONF (Network Configuration) Working Group";
contact
"WG Web: <http://tools.ietf.org/wg/netconf/>
WG List: <netconf@ietf.org>
WG Chair: Bert Wijnen
<bertietf@bwijnen.net>
WG Chair: Mehmet Ersue
<mehmet.ersue@nsn.com>
Editor: Martin Bjorklund
<mbj@tail-f.com>
Editor: Juergen Schoenwaelder
<j.schoenwaelder@jacobs-university.de>
Editor: Andy Bierman
<andy.bierman@brocade.com>";
description
"NETCONF Protocol Data Types and Protocol Operations.
Copyright (c) 2011 IETF Trust and the persons identified as
the document authors. All rights reserved.
Redistribution and use in source and binary forms, with or
without modification, is permitted pursuant to, and subject
to the license terms contained in, the Simplified BSD License
set forth in Section 4.c of the IETF Trust's Legal Provisions
Relating to IETF Documents
(http://trustee.ietf.org/license-info).
This version of this YANG module is part of RFC 6241; see
the RFC itself for full legal notices.";
revision 2011-06-01 {
description
"Initial revision";
reference
"RFC 6241: Network Configuration Protocol";
}
extension get-filter-element-attributes {
description
"If this extension is present within an 'anyxml'
statement named 'filter', which must be conceptually
defined within the RPC input section for the <get>
and <get-config> protocol operations, then the
following unqualified XML attribute is supported
within the <filter> element, within a <get> or
<get-config> protocol operation:
type : optional attribute with allowed
value strings 'subtree' and 'xpath'.
If missing, the default value is 'subtree'.
If the 'xpath' feature is supported, then the
following unqualified XML attribute is
also supported:
select: optional attribute containing a
string representing an XPath expression.
The 'type' attribute must be equal to 'xpath'
if this attribute is present.";
}
// NETCONF capabilities defined as features
feature writable-running {
description
"NETCONF :writable-running capability;
If the server advertises the :writable-running
capability for a session, then this feature must
also be enabled for that session. Otherwise,
this feature must not be enabled.";
reference "RFC 6241, Section 8.2";
}
feature candidate {
description
"NETCONF :candidate capability;
If the server advertises the :candidate
capability for a session, then this feature must
also be enabled for that session. Otherwise,
this feature must not be enabled.";
reference "RFC 6241, Section 8.3";
}
feature confirmed-commit {
if-feature candidate;
description
"NETCONF :confirmed-commit:1.1 capability;
If the server advertises the :confirmed-commit:1.1
capability for a session, then this feature must
also be enabled for that session. Otherwise,
this feature must not be enabled.";
reference "RFC 6241, Section 8.4";
}
feature rollback-on-error {
description
"NETCONF :rollback-on-error capability;
If the server advertises the :rollback-on-error
capability for a session, then this feature must
also be enabled for that session. Otherwise,
this feature must not be enabled.";
reference "RFC 6241, Section 8.5";
}
feature validate {
description
"NETCONF :validate:1.1 capability;
If the server advertises the :validate:1.1
capability for a session, then this feature must
also be enabled for that session. Otherwise,
this feature must not be enabled.";
reference "RFC 6241, Section 8.6";
}
feature startup {
description
"NETCONF :startup capability;
If the server advertises the :startup
capability for a session, then this feature must
also be enabled for that session. Otherwise,
this feature must not be enabled.";
reference "RFC 6241, Section 8.7";
}
feature url {
description
"NETCONF :url capability;
If the server advertises the :url
capability for a session, then this feature must
also be enabled for that session. Otherwise,
this feature must not be enabled.";
reference "RFC 6241, Section 8.8";
}
feature xpath {
description
"NETCONF :xpath capability;
If the server advertises the :xpath
capability for a session, then this feature must
also be enabled for that session. Otherwise,
this feature must not be enabled.";
reference "RFC 6241, Section 8.9";
}
// NETCONF Simple Types
typedef session-id-type {
type uint32 {
range "1..max";
}
description
"NETCONF Session Id";
}
typedef session-id-or-zero-type {
type uint32;
description
"NETCONF Session Id or Zero to indicate none";
}
typedef error-tag-type {
type enumeration {
enum in-use {
description
"The request requires a resource that
already is in use.";
}
enum invalid-value {
description
"The request specifies an unacceptable value for one
or more parameters.";
}
enum too-big {
description
"The request or response (that would be generated) is
too large for the implementation to handle.";
}
enum missing-attribute {
description
"An expected attribute is missing.";
}
enum bad-attribute {
description
"An attribute value is not correct; e.g., wrong type,
out of range, pattern mismatch.";
}
enum unknown-attribute {
description
"An unexpected attribute is present.";
}
enum missing-element {
description
"An expected element is missing.";
}
enum bad-element {
description
"An element value is not correct; e.g., wrong type,
out of range, pattern mismatch.";
}
enum unknown-element {
description
"An unexpected element is present.";
}
enum unknown-namespace {
description
"An unexpected namespace is present.";
}
enum access-denied {
description
"Access to the requested protocol operation or
data model is denied because authorization failed.";
}
enum lock-denied {
description
"Access to the requested lock is denied because the
lock is currently held by another entity.";
}
enum resource-denied {
description
"Request could not be completed because of
insufficient resources.";
}
enum rollback-failed {
description
"Request to roll back some configuration change (via
rollback-on-error or <discard-changes> operations)
was not completed for some reason.";
}
enum data-exists {
description
"Request could not be completed because the relevant
data model content already exists. For example,
a 'create' operation was attempted on data that
already exists.";
}
enum data-missing {
description
"Request could not be completed because the relevant
data model content does not exist. For example,
a 'delete' operation was attempted on
data that does not exist.";
}
enum operation-not-supported {
description
"Request could not be completed because the requested
operation is not supported by this implementation.";
}
enum operation-failed {
description
"Request could not be completed because the requested
operation failed for some reason not covered by
any other error condition.";
}
enum partial-operation {
description
"This error-tag is obsolete, and SHOULD NOT be sent
by servers conforming to this document.";
}
enum malformed-message {
description
"A message could not be handled because it failed to
be parsed correctly. For example, the message is not
well-formed XML or it uses an invalid character set.";
}
}
description "NETCONF Error Tag";
reference "RFC 6241, Appendix A";
}
typedef error-severity-type {
type enumeration {
enum error {
description "Error severity";
}
enum warning {
description "Warning severity";
}
}
description "NETCONF Error Severity";
reference "RFC 6241, Section 4.3";
}
typedef edit-operation-type {
type enumeration {
enum merge {
description
"The configuration data identified by the
element containing this attribute is merged
with the configuration at the corresponding
level in the configuration datastore identified
by the target parameter.";
}
enum replace {
description
"The configuration data identified by the element
containing this attribute replaces any related
configuration in the configuration datastore
identified by the target parameter. If no such
configuration data exists in the configuration
datastore, it is created. Unlike a
<copy-config> operation, which replaces the
entire target configuration, only the configuration
actually present in the config parameter is affected.";
}
enum create {
description
"The configuration data identified by the element
containing this attribute is added to the
configuration if and only if the configuration
data does not already exist in the configuration
datastore. If the configuration data exists, an
<rpc-error> element is returned with an
<error-tag> value of 'data-exists'.";
}
enum delete {
description
"The configuration data identified by the element
containing this attribute is deleted from the
configuration if and only if the configuration
data currently exists in the configuration
datastore. If the configuration data does not
exist, an <rpc-error> element is returned with
an <error-tag> value of 'data-missing'.";
}
enum remove {
description
"The configuration data identified by the element
containing this attribute is deleted from the
configuration if the configuration
data currently exists in the configuration
datastore. If the configuration data does not
exist, the 'remove' operation is silently ignored
by the server.";
}
}
default "merge";
description "NETCONF 'operation' attribute values";
reference "RFC 6241, Section 7.2";
}
// NETCONF Standard Protocol Operations
rpc get-config {
description
"Retrieve all or part of a specified configuration.";
reference "RFC 6241, Section 7.1";
input {
container source {
description
"Particular configuration to retrieve.";
choice config-source {
mandatory true;
description
"The configuration to retrieve.";
leaf candidate {
if-feature candidate;
type empty;
description
"The candidate configuration is the config source.";
}
leaf running {
type empty;
description
"The running configuration is the config source.";
}
leaf startup {
if-feature startup;
type empty;
description
"The startup configuration is the config source.
This is optional-to-implement on the server because
not all servers will support filtering for this
datastore.";
}
}
}
anyxml filter {
description
"Subtree or XPath filter to use.";
nc:get-filter-element-attributes;
}
}
output {
anyxml data {
description
"Copy of the source datastore subset that matched
the filter criteria (if any). An empty data container
indicates that the request did not produce any results.";
}
}
}
rpc edit-config {
description
"The <edit-config> operation loads all or part of a specified
configuration to the specified target configuration.";
reference "RFC 6241, Section 7.2";
input {
container target {
description
"Particular configuration to edit.";
choice config-target {
mandatory true;
description
"The configuration target.";
leaf candidate {
if-feature candidate;
type empty;
description
"The candidate configuration is the config target.";
}
leaf running {
if-feature writable-running;
type empty;
description
"The running configuration is the config source.";
}
}
}
leaf default-operation {
type enumeration {
enum merge {
description
"The default operation is merge.";
}
enum replace {
description
"The default operation is replace.";
}
enum none {
description
"There is no default operation.";
}
}
default "merge";
description
"The default operation to use.";
}
leaf test-option {
if-feature validate;
type enumeration {
enum test-then-set {
description
"The server will test and then set if no errors.";
}
enum set {
description
"The server will set without a test first.";
}
enum test-only {
description
"The server will only test and not set, even
if there are no errors.";
}
}
default "test-then-set";
description
"The test option to use.";
}
leaf error-option {
type enumeration {
enum stop-on-error {
description
"The server will stop on errors.";
}
enum continue-on-error {
description
"The server may continue on errors.";
}
enum rollback-on-error {
description
"The server will roll back on errors.
This value can only be used if the 'rollback-on-error'
feature is supported.";
}
}
default "stop-on-error";
description
"The error option to use.";
}
choice edit-content {
mandatory true;
description
"The content for the edit operation.";
anyxml config {
description
"Inline Config content.";
}
leaf url {
if-feature url;
type inet:uri;
description
"URL-based config content.";
}
}
}
}
rpc copy-config {
description
"Create or replace an entire configuration datastore with the
contents of another complete configuration datastore.";
reference "RFC 6241, Section 7.3";
input {
container target {
description
"Particular configuration to copy to.";
choice config-target {
mandatory true;
description
"The configuration target of the copy operation.";
leaf candidate {
if-feature candidate;
type empty;
description
"The candidate configuration is the config target.";
}
leaf running {
if-feature writable-running;
type empty;
description
"The running configuration is the config target.
This is optional-to-implement on the server.";
}
leaf startup {
if-feature startup;
type empty;
description
"The startup configuration is the config target.";
}
leaf url {
if-feature url;
type inet:uri;
description
"The URL-based configuration is the config target.";
}
}
}
container source {
description
"Particular configuration to copy from.";
choice config-source {
mandatory true;
description
"The configuration source for the copy operation.";
leaf candidate {
if-feature candidate;
type empty;
description
"The candidate configuration is the config source.";
}
leaf running {
type empty;
description
"The running configuration is the config source.";
}
leaf startup {
if-feature startup;
type empty;
description
"The startup configuration is the config source.";
}
leaf url {
if-feature url;
type inet:uri;
description
"The URL-based configuration is the config source.";
}
anyxml config {
description
"Inline Config content: <config> element. Represents
an entire configuration datastore, not
a subset of the running datastore.";
}
}
}
}
}
rpc delete-config {
description
"Delete a configuration datastore.";
reference "RFC 6241, Section 7.4";
input {
container target {
description
"Particular configuration to delete.";
choice config-target {
mandatory true;
description
"The configuration target to delete.";
leaf startup {
if-feature startup;
type empty;
description
"The startup configuration is the config target.";
}
leaf url {
if-feature url;
type inet:uri;
description
"The URL-based configuration is the config target.";
}
}
}
}
}
rpc lock {
description
"The lock operation allows the client to lock the configuration
system of a device.";
reference "RFC 6241, Section 7.5";
input {
container target {
description
"Particular configuration to lock.";
choice config-target {
mandatory true;
description
"The configuration target to lock.";
leaf candidate {
if-feature candidate;
type empty;
description
"The candidate configuration is the config target.";
}
leaf running {
type empty;
description
"The running configuration is the config target.";
}
leaf startup {
if-feature startup;
type empty;
description
"The startup configuration is the config target.";
}
}
}
}
}
rpc unlock {
description
"The unlock operation is used to release a configuration lock,
previously obtained with the 'lock' operation.";
reference "RFC 6241, Section 7.6";
input {
container target {
description
"Particular configuration to unlock.";
choice config-target {
mandatory true;
description
"The configuration target to unlock.";
leaf candidate {
if-feature candidate;
type empty;
description
"The candidate configuration is the config target.";
}
leaf running {
type empty;
description
"The running configuration is the config target.";
}
leaf startup {
if-feature startup;
type empty;
description
"The startup configuration is the config target.";
}
}
}
}
}
rpc get {
description
"Retrieve running configuration and device state information.";
reference "RFC 6241, Section 7.7";
input {
anyxml filter {
description
"This parameter specifies the portion of the system
configuration and state data to retrieve.";
nc:get-filter-element-attributes;
}
}
output {
anyxml data {
description
"Copy of the running datastore subset and/or state
data that matched the filter criteria (if any).
An empty data container indicates that the request did not
produce any results.";
}
}
}
rpc close-session {
description
"Request graceful termination of a NETCONF session.";
reference "RFC 6241, Section 7.8";
}
rpc kill-session {
description
"Force the termination of a NETCONF session.";
reference "RFC 6241, Section 7.9";
input {
leaf session-id {
type session-id-type;
mandatory true;
description
"Particular session to kill.";
}
}
}
rpc commit {
if-feature candidate;
description
"Commit the candidate configuration as the device's new
current configuration.";
reference "RFC 6241, Section 8.3.4.1";
input {
leaf confirmed {
if-feature confirmed-commit;
type empty;
description
"Requests a confirmed commit.";
reference "RFC 6241, Section 8.3.4.1";
}
leaf confirm-timeout {
if-feature confirmed-commit;
type uint32 {
range "1..max";
}
units "seconds";
default "600"; // 10 minutes
description
"The timeout interval for a confirmed commit.";
reference "RFC 6241, Section 8.3.4.1";
}
leaf persist {
if-feature confirmed-commit;
type string;
description
"This parameter is used to make a confirmed commit
persistent. A persistent confirmed commit is not aborted
if the NETCONF session terminates. The only way to abort
a persistent confirmed commit is to let the timer expire,
or to use the <cancel-commit> operation.
The value of this parameter is a token that must be given
in the 'persist-id' parameter of <commit> or
<cancel-commit> operations in order to confirm or cancel
the persistent confirmed commit.
The token should be a random string.";
reference "RFC 6241, Section 8.3.4.1";
}
leaf persist-id {
if-feature confirmed-commit;
type string;
description
"This parameter is given in order to commit a persistent
confirmed commit. The value must be equal to the value
given in the 'persist' parameter to the <commit> operation.
If it does not match, the operation fails with an
'invalid-value' error.";
reference "RFC 6241, Section 8.3.4.1";
}
}
}
rpc discard-changes {
if-feature candidate;
description
"Revert the candidate configuration to the current
running configuration.";
reference "RFC 6241, Section 8.3.4.2";
}
rpc cancel-commit {
if-feature confirmed-commit;
description
"This operation is used to cancel an ongoing confirmed commit.
If the confirmed commit is persistent, the parameter
'persist-id' must be given, and it must match the value of the
'persist' parameter.";
reference "RFC 6241, Section 8.4.4.1";
input {
leaf persist-id {
type string;
description
"This parameter is given in order to cancel a persistent
confirmed commit. The value must be equal to the value
given in the 'persist' parameter to the <commit> operation.
If it does not match, the operation fails with an
'invalid-value' error.";
}
}
}
rpc validate {
if-feature validate;
description
"Validates the contents of the specified configuration.";
reference "RFC 6241, Section 8.6.4.1";
input {
container source {
description
"Particular configuration to validate.";
choice config-source {
mandatory true;
description
"The configuration source to validate.";
leaf candidate {
if-feature candidate;
type empty;
description
"The candidate configuration is the config source.";
}
leaf running {
type empty;
description
"The running configuration is the config source.";
}
leaf startup {
if-feature startup;
type empty;
description
"The startup configuration is the config source.";
}
leaf url {
if-feature url;
type inet:uri;
description
"The URL-based configuration is the config source.";
}
anyxml config {
description
"Inline Config content: <config> element. Represents
an entire configuration datastore, not
a subset of the running datastore.";
}
}
}
}
}
}