diff --git a/CHANGELOG.md b/CHANGELOG.md index 54b51188..ac8db0a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Clixon CHANGELOG -- Yang anyxml and extensions basic support added +- Added generic xml_merge() function. + +- Added new backend plugin callback: "plugin_statedata()" for getting state data - Added yang dir with ietf-netconf and clixon-config yang specs for internal usage. diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index f618029a..b9548b49 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -242,7 +242,6 @@ from_client_get_config(clicon_handle h, 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) @@ -265,6 +264,7 @@ from_client_get_config(clicon_handle h, * @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, @@ -279,6 +279,7 @@ from_client_get(clicon_handle h, 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, "" "operation-failed" @@ -288,8 +289,12 @@ from_client_get(clicon_handle h, ""); 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, ""); - /* if empty only , if any data then .. */ + /* if empty only */ if (xret!=NULL){ if (xml_child_nr(xret)){ if (xml_name_set(xret, "config") < 0) @@ -984,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, "" diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index ab9843d6..ec560db4 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -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) diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index 1a74fb7a..9bb6b131 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -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, 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; inr_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: . * @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; } diff --git a/apps/backend/clixon_backend_handle.h b/apps/backend/clixon_backend_handle.h index b309f2a2..0bca3835 100644 --- a/apps/backend/clixon_backend_handle.h +++ b/apps/backend/clixon_backend_handle.h @@ -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: */ - struct client_entry *ce, /* Client session */ - cbuf *cbret, /* Reply eg ... */ - void *arg /* Argument given at register */ + cxobj *xe, /* Request: */ + struct client_entry *ce, /* Client session */ + cbuf *cbret,/* Reply eg ... */ + 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_ */ diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 664b4f44..1f0073f7 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -180,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, "

Not Implemented/h1>\n"); + return 0; +} + int conflict(FCGX_Request *r) { diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index c9d78d76..1ebcb74a 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -47,6 +47,7 @@ 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); diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 2b915b9b..9e2f4db5 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -171,7 +171,7 @@ 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; } @@ -541,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; } diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index 2c05f6b5..a6357508 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -331,7 +331,6 @@ text_get(xmldb_handle xh, 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; @@ -460,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 */ diff --git a/example/routing_backend.c b/example/routing_backend.c index 34e6fc80..c1ea24bc 100644 --- a/example/routing_backend.c +++ b/example/routing_backend.c @@ -129,6 +129,40 @@ routing_downcall(clicon_handle h, cprintf(cbret, "%s", 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, 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 statedata, remove 0 to enable */ + if (0 && (xml_parse("" + "eth0" + "eth" + "up" + "up" + "42" + "1000000000" + "", 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: diff --git a/lib/clixon/clixon_proto_client.h b/lib/clixon/clixon_proto_client.h index c19997d1..ca690b9f 100644 --- a/lib/clixon/clixon_proto_client.h +++ b/lib/clixon/clixon_proto_client.h @@ -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); diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index e98887ff..f4ed4c07 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -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); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index af158960..aba551bf 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -73,5 +73,6 @@ 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_ */ diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index e0bc54eb..9c111676 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -476,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 or . + * @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, ""); + if (xpath && strlen(xpath)) + cprintf(cb, "", xpath); + cprintf(cb, ""); + 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 */ diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index ee4cd667..44a9dcf4 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -466,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, @@ -511,7 +516,6 @@ xml_new_spec(char *name, return x; } - void * xml_spec(cxobj *x) { @@ -961,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) { @@ -1181,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; diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index ef88717a..19b2e12e 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -323,22 +323,17 @@ xmldb_setopt(clicon_handle h, * @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() + * @param[out] xtop Single XML tree. Free with xml_free() * @retval 0 OK * @retval -1 Error * @code * cxobj *xt; * if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]", 1, &xt) < 0) * err; - * for (i=0; iys_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: [ ]* */ + 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; +} diff --git a/lib/src/clixon_xsl.c b/lib/src/clixon_xsl.c index 018ff29a..5928a4c6 100644 --- a/lib/src/clixon_xsl.c +++ b/lib/src/clixon_xsl.c @@ -925,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]]>]]>" new "netconf edit state operation should fail" expecteof "$clixon_netconf -qf $clixon_cf" "eth1eth]]>]]>" "^invalid-value" +new "netconf get state operation" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" + new "netconf lock/unlock" expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>]]>]]>" "^]]>]]>]]>]]>$" diff --git a/test/test4.sh b/test/test4.sh index bbb048a6..4ce09d69 100755 --- a/test/test4.sh +++ b/test/test4.sh @@ -82,7 +82,7 @@ new "netconf get leaf-list path" 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]]>]]>$" +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" "" diff --git a/test/test6.sh b/test/test6.sh new file mode 100755 index 00000000..e180e7b9 --- /dev/null +++ b/test/test6.sh @@ -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 < /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" "]]>]]>" "^]]>]]>$" + +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