diff --git a/CHANGELOG.md b/CHANGELOG.md index 66185599..fd607723 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Clixon CHANGELOG +- Added state data: Netconf operation introduced; Error when + adding state data in . + +- 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 diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 303e19b3..f618029a 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -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, "" "operation-failed" "application" @@ -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, "" + "operation-failed" + "application" + "error" + "read-registry" + ""); + goto ok; + } + cprintf(cbret, ""); + /* if empty only , if any data then .. */ + 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, ""); + 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, "", 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, "" + "invalid-value" + "protocol" + "error" + "state data not allowed" + ""); + goto ok; + } + if (xmldb_put(h, target, operation, xc) < 0){ cprintf(cbret, "" "operation-failed" @@ -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, ""); diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index ea352d85..ec0b0887 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -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 */ diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index ac631765..a507bd9c 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -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); diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index b63af86c..56fd81bf 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -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 ... 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 + * + * ]]>]]> + */ +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 ... */ + 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, "" + "operation-failed" + "applicatio" + "error" + "filtering" + ""); + } + } + } + else{ + clicon_xml_parse(xret, "" + "operation-failed" + "applicatio" + "error" + "filter type not supported" + "type" + ""); + } + // ok: /* netconf error is not fatal */ + retval = 0; + done: + return retval; +} + + /*! Close a (user) session * @param[in] xn Sub-tree (under xorig) at ... 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) diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index dd1d6d20..664b4f44 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -57,6 +57,96 @@ #include "restconf_lib.h" +/* See RFC 8040 Section 7: Mapping from NETCONF 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); +} /*! */ diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index b0cdf7c9..c9d78d76 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -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); diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 635d91c4..2b915b9b 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -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); diff --git a/datastore/datastore_client.c b/datastore/datastore_client.c index 368f59bd..82d49e69 100644 --- a/datastore/datastore_client.c +++ b/datastore/datastore_client.c @@ -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); diff --git a/datastore/keyvalue/clixon_keyvalue.c b/datastore/keyvalue/clixon_keyvalue.c index 5739c78f..56b7196b 100644 --- a/datastore/keyvalue/clixon_keyvalue.c +++ b/datastore/keyvalue/clixon_keyvalue.c @@ -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) diff --git a/datastore/keyvalue/clixon_keyvalue.h b/datastore/keyvalue/clixon_keyvalue.h index d46de46f..83654df0 100644 --- a/datastore/keyvalue/clixon_keyvalue.h +++ b/datastore/keyvalue/clixon_keyvalue.h @@ -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); diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index e35b5c85..2c05f6b5 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -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,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; @@ -337,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; } @@ -362,7 +327,7 @@ text_get(xmldb_handle xh, goto done; } /* Here xt looks like: ... */ - /* 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; i1) 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) @@ -842,7 +812,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 @@ -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); } diff --git a/datastore/text/clixon_xmldb_text.h b/datastore/text/clixon_xmldb_text.h index b4487757..c3c24c22 100644 --- a/datastore/text/clixon_xmldb_text.h +++ b/datastore/text/clixon_xmldb_text.h @@ -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); diff --git a/lib/clixon/clixon_xml_db.h b/lib/clixon/clixon_xml_db.h index 0a2ce81a..23a04ca0 100644 --- a/lib/clixon/clixon_xml_db.h +++ b/lib/clixon/clixon_xml_db.h @@ -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); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 6a24a5a0..af158960 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -47,8 +47,6 @@ enum { LVXML_VECVAL, /* key: a.b.0{x=1} -> 1 och */ LVXML_VECVAL2, /* key: a.b.0{x=1} -> 1 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); diff --git a/lib/src/clixon_hash.c b/lib/src/clixon_hash.c index fc914b60..3ad70036 100644 --- a/lib/src/clixon_hash.c +++ b/lib/src/clixon_hash.c @@ -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 diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index fc9b11d9..ee4cd667 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -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,10 +1400,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; } @@ -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; diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index 08becf39..ef88717a 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -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; ixa_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, diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index bca2d162..ed532d8f 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -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 @@ -1618,83 +1717,5 @@ api_path2xml(char *api_path, return retval; } -/* See RFC 8040 Section 7: Mapping from NETCONF 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} -}; diff --git a/lib/src/clixon_xsl.c b/lib/src/clixon_xsl.c index 8963f803..018ff29a 100644 --- a/lib/src/clixon_xsl.c +++ b/lib/src/clixon_xsl.c @@ -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: diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c index d3028b72..8fc59ed0 100644 --- a/lib/src/clixon_yang_type.c +++ b/lib/src/clixon_yang_type.c @@ -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; } diff --git a/test/test2.sh b/test/test2.sh index 8099f11f..8fba9063 100755 --- a/test/test2.sh +++ b/test/test2.sh @@ -86,6 +86,9 @@ expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" +new "netconf edit state operation should fail" +expecteof "$clixon_netconf -qf $clixon_cf" "eth1eth]]>]]>" "^]]>]]>$" + new "netconf lock/unlock" expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>]]>]]>" "^]]>]]>]]>]]>$" diff --git a/test/test4.sh b/test/test4.sh index fb21a3bf..bbb048a6 100755 --- a/test/test4.sh +++ b/test/test4.sh @@ -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 < /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" "]]>]]>" "^hejhopp]]>]]>$" +new "netconf get (state data XXX should be some)" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "]]>]]>" "^125]]>]]>$" + +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" "foo" + +new "netconf set state data (not allowed)" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "42]]>]]>" "^invalid-value" + + new "Kill backend" # Check if still alive pid=`pgrep clixon_backend` diff --git a/test/test5.sh b/test/test5.sh index 148ed1ab..acf6af59 100755 --- a/test/test5.sh +++ b/test/test5.sh @@ -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 < /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 13newentry" + 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 }