Added state data

This commit is contained in:
Olof hagsand 2017-07-01 17:42:39 +02:00
parent 43c39160a5
commit f5c19d8586
24 changed files with 512 additions and 224 deletions

View file

@ -1,5 +1,10 @@
# Clixon CHANGELOG
- 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
@ -12,7 +17,7 @@
- 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

@ -232,7 +232,7 @@ from_client_get_config(clicon_handle h,
if ((xfilter = xml_find(xe, "filter")) != NULL)
if ((selector = xml_find_value(xfilter, "select"))==NULL)
selector="/";
if (xmldb_get(h, db, selector, &xret) < 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>"
@ -260,6 +260,54 @@ 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
*/
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="/";
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;
}
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)
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
@ -281,7 +329,13 @@ 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;
@ -295,7 +349,6 @@ from_client_edit_config(clicon_handle h,
"</rpc-error></rpc-reply>", target);
goto ok;
}
/* Check if target locked by other client */
piddb = xmldb_islocked(h, target);
if (piddb && mypid != piddb){
@ -320,6 +373,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>"
@ -875,6 +942,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>");

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) < 0)
if (xmldb_get(h, "running", "/", 1, &td->td_src) < 0)
goto done;
if (xmldb_get(h, candidate, "/", &td->td_target) < 0)
if (xmldb_get(h, candidate, "/", 1, &td->td_target) < 0)
goto done;
/* 3. Compute differences */

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);

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

@ -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);
}
/*!
*/

View file

@ -43,6 +43,8 @@
/*
* Prototypes
*/
int restconf_err2code(char *tag);
const char *restconf_code2reason(int code);
int notfound(FCGX_Request *r);
int badrequest(FCGX_Request *r);
int conflict(FCGX_Request *r);

View file

@ -190,8 +190,8 @@ api_data_get_gen(clicon_handle h,
notfound(r); /* bad reply? */
goto done;
}
code = clicon_str2int(netconf_restconf_map, xml_body(xtag));
if ((reason_phrase = clicon_int2str(http_reason_phrase_map, code)) == NULL)
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);

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) < 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)
@ -578,6 +567,7 @@ int
kv_get(xmldb_handle xh,
char *db,
char *xpath,
int config,
cxobj **xtop)
{
int retval = -1;
@ -627,13 +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;
/* 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)

View file

@ -39,7 +39,7 @@
/*
* Prototypes
*/
int kv_get(xmldb_handle h, char *db, char *xpath, cxobj **xtop);
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

@ -71,6 +71,7 @@ struct text_handle {
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,16 +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"
@ -153,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;
@ -172,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;
@ -239,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"
*/
@ -319,6 +283,7 @@ int
text_get(xmldb_handle xh,
char *db,
char *xpath,
int config,
cxobj **xtop)
{
int retval = -1;
@ -362,7 +327,7 @@ 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;
@ -370,28 +335,34 @@ text_get(xmldb_handle xh,
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);
@ -696,7 +667,6 @@ text_modify_top(cxobj *x0,
return retval;
}
/*! Modify database provided an xml tree and an operation
* This is a clixon datastore plugin of the the xmldb api
* @see xmldb_put
@ -757,11 +727,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;
@ -772,7 +742,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)
@ -853,13 +823,9 @@ text_lock(xmldb_handle xh,
char *db,
int pid)
{
// 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;
struct text_handle *th = handle(xh);
hash_add(th->th_dbs, db, &pid, sizeof(pid));
clicon_debug(1, "%s: locked by %u", db, pid);
return 0;
}
@ -876,13 +842,11 @@ int
text_unlock(xmldb_handle xh,
char *db)
{
// struct text_handle *th = handle(xh);
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;
struct text_handle *th = handle(xh);
int zero = 0;
hash_add(th->th_dbs, db, &zero, sizeof(zero));
// hash_del(th->th_dbs, db);
return 0;
}
@ -896,14 +860,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;
}
@ -918,14 +887,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;
}
@ -1022,6 +990,7 @@ text_plugin_exit(void)
return 0;
}
static const struct xmldb_api api;
static const struct xmldb_api api;
/*! plugin init function */
@ -1111,7 +1080,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,7 +39,7 @@
/*
* Prototypes
*/
int text_get(xmldb_handle h, char *db, char *xpath, cxobj **xtop);
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

@ -75,7 +75,7 @@ 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);
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);
@ -138,7 +138,7 @@ 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);
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

@ -47,8 +47,6 @@ enum {
LVXML_VECVAL, /* key: a.b.0{x=1} -> <a><b><x>1</x></b></a> och */
LVXML_VECVAL2, /* key: a.b.0{x=1} -> <a><x>1</x></a> och */
};
extern const map_str2int netconf_restconf_map[];
extern const map_str2int http_reason_phrase_map[];
/*
* Prototypes
@ -65,10 +63,13 @@ 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);

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

@ -1345,6 +1345,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)
* {
@ -1353,7 +1357,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
@ -1363,13 +1367,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:
@ -1377,7 +1387,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,
@ -1386,9 +1400,13 @@ xml_apply0(cxobj *xn,
void *arg)
{
int retval = -1;
int ret;
if (fn(xn, arg) < 0)
if ((ret = fn(xn, arg)) < 0)
goto done;
if (ret > 0)
retval = ret;
else
retval = xml_apply(xn, type, fn, arg);
done:
return retval;
@ -1402,6 +1420,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)
* {
@ -1420,12 +1441,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

@ -322,12 +322,13 @@ 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[in] config If set only configuration data, else also state
* @param[out] xtop Single XML tree which xvec points to. Free with xml_free()
* @retval 0 OK
* @retval -1 Error
* @code
* cxobj *xt;
* if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]", &xt) < 0)
* if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]", 1, &xt) < 0)
* err;
* for (i=0; i<xlen; i++){
* xn = xv[i];
@ -343,6 +344,7 @@ int
xmldb_get(clicon_handle h,
char *db,
char *xpath,
int config,
cxobj **xtop)
{
int retval = -1;
@ -361,7 +363,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);
retval = xa->xa_get_fn(xh, db, xpath, config, xtop);
#if DEBUG
if (retval == 0) {
cbuf *cb = cbuf_new();
@ -391,6 +393,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,

View file

@ -1084,20 +1084,22 @@ 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,
xml_tree_prune_flagged_sub(cxobj *xt,
int flag,
int test,
int *upmark)
@ -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
@ -1618,83 +1717,5 @@ api_path2xml(char *api_path,
return retval;
}
/* See RFC 8040 Section 7: Mapping from NETCONF<error-tag> to Status Code
* and RFC 6241 Appendix A. NETCONF Error list
*/
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
*/
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}
};

View file

@ -132,7 +132,7 @@ enum axis_type{
};
/* Mapping between axis type string <--> int */
static const map_str2int atmap[] = {
static const map_str2int axismap[] = {
{"self", A_SELF},
{"child", A_CHILD},
{"parent", A_PARENT},
@ -163,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 ", clicon_int2str(atmap, 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);
@ -581,7 +581,7 @@ xpath_find(struct xpath_element *xe,
}
#if 0
fprintf(stderr, "%s: %s: \"%s\"\n", __FUNCTION__,
clicon_int2str(atmap, 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:

View file

@ -103,12 +103,14 @@ static const map_str2int ytmap[] = {
static int
yang_builtin(char *type)
{
if (clicon_str2int(ytmap, type) != -1)
return 1;
#if 0
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;
#endif
return 0;
}

View file

@ -86,6 +86,9 @@ 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><ok/></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><source><candidate/></source><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>"
#leaf-list
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
}