diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in index 4c55d23f..773a7d48 100644 --- a/apps/backend/Makefile.in +++ b/apps/backend/Makefile.in @@ -85,6 +85,7 @@ APPL = clixon_backend APPSRC = backend_main.c APPSRC += backend_socket.c APPSRC += backend_client.c +APPSRC += backend_get.c APPSRC += backend_plugin_restconf.c # Pseudo plugin for restconf daemon APPSRC += backend_startup.c APPOBJ = $(APPSRC:.c=.o) diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index f6d16f9d..e28647cd 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -69,8 +69,9 @@ #include "clixon_backend_handle.h" #include "clixon_backend_plugin.h" #include "clixon_backend_commit.h" -#include "backend_client.h" #include "backend_handle.h" +#include "backend_get.h" +#include "backend_client.h" /*! Find client by session-id * @param[in] ce_list List of clients @@ -156,108 +157,6 @@ backend_client_rm(clicon_handle h, return backend_client_delete(h, ce); /* actually purge it */ } -/*! - * Maybe should be in the restconf client instead of backend? - * @param[in] h Clicon handle - * @param[in] yspec Yang spec - * @param[in] xpath Xpath selection, not used but may be to filter early - * @param[out] xrs XML restconf-state node - * @see netconf_hello_server - * @see rfc8040 Sections 9.1 - */ -static int -client_get_capabilities(clicon_handle h, - yang_stmt *yspec, - char *xpath, - cxobj **xret) -{ - int retval = -1; - cxobj *xrstate = NULL; /* xml restconf-state node */ - cbuf *cb = NULL; - - if ((xrstate = xpath_first(*xret, NULL, "restconf-state")) == NULL){ - clicon_err(OE_YANG, ENOENT, "restconf-state not found in config node"); - goto done; - } - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cb, ""); - cprintf(cb, "urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit"); - cprintf(cb, "urn:ietf:params:restconf:capability:depth:1.0"); - cprintf(cb, ""); - if (clixon_xml_parse_string(cbuf_get(cb), YB_PARENT, NULL, &xrstate, NULL) < 0) - goto done; - retval = 0; - done: - if (cb) - cbuf_free(cb); - return retval; -} - -/*! Get streams state according to RFC 8040 or RFC5277 common function - * @param[in] h Clicon handle - * @param[in] yspec Yang spec - * @param[in] xpath Xpath selection, not used but may be to filter early - * @param[in] module Name of yang module - * @param[in] top Top symbol, ie netconf or restconf-state - * @param[in,out] xret Existing XML tree, merge x into this - * @retval -1 Error (fatal) - * @retval 0 Statedata callback failed - * @retval 1 OK - */ -static int -client_get_streams(clicon_handle h, - yang_stmt *yspec, - char *xpath, - yang_stmt *ymod, - char *top, - cxobj **xret) -{ - int retval = -1; - yang_stmt *yns = NULL; /* yang namespace */ - cxobj *x = NULL; - cbuf *cb = NULL; - int ret; - - if ((yns = yang_find(ymod, Y_NAMESPACE, NULL)) == NULL){ - clicon_err(OE_YANG, 0, "%s yang namespace not found", yang_argument_get(ymod)); - goto done; - } - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cb, "<%s xmlns=\"%s\">", top, yang_argument_get(yns)); - /* Second argument is a hack to have the same function for the - * RFC5277 and 8040 stream cases - */ - if (stream_get_xml(h, strcmp(top,"restconf-state")==0, cb) < 0) - goto done; - cprintf(cb,"", top); - - if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x, NULL) < 0){ - if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) - goto done; - goto fail; - } - if ((ret = netconf_trymerge(x, yspec, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - retval = 1; - done: - if (cb) - cbuf_free(cb); - if (x) - xml_free(x); - return retval; - fail: - retval = 0; - goto done; -} - /*! Get clixon per datastore stats * @param[in] h Clicon handle * @param[in] dbname Datastore name @@ -292,445 +191,6 @@ clixon_stats_get_db(clicon_handle h, return retval; } -/*! Get system state-data, including streams and plugins - * @param[in] h Clicon handle - * @param[in] xpath XPath selection, may be used to filter early - * @param[in] nsc XML Namespace context for xpath - * @param[in] content config/state or both - * @param[in,out] xret Existing XML tree, merge x into this - * @retval -1 Error (fatal) - * @retval 0 Statedata callback failed (clicon_err called) - * @retval 1 OK - */ -static int -client_statedata(clicon_handle h, - char *xpath, - cvec *nsc, - netconf_content content, - cxobj **xret) -{ - int retval = -1; - yang_stmt *yspec; - yang_stmt *ymod; - int ret; - char *namespace; - cbuf *cb = NULL; - - clicon_debug(1, "%s", __FUNCTION__); - if ((yspec = clicon_dbspec_yang(h)) == NULL){ - clicon_err(OE_YANG, ENOENT, "No yang spec"); - goto done; - } - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - /* Add default state to config if present */ - if (xml_default_recurse(*xret, 1) < 0) - goto done; - /* Add default global state */ - if (xml_global_defaults(h, *xret, nsc, xpath, yspec, 1) < 0) - goto done; - - if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277")){ - if ((ymod = yang_find_module_by_name(yspec, "clixon-rfc5277")) == NULL){ - clicon_err(OE_YANG, ENOENT, "yang module clixon-rfc5277 not found"); - goto done; - } - if ((namespace = yang_find_mynamespace(ymod)) == NULL){ - clicon_err(OE_YANG, ENOENT, "clixon-rfc5277 namespace not found"); - goto done; - } - cprintf(cb, "", namespace); - if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, xret, NULL) < 0) - goto done; - if ((ret = client_get_streams(h, yspec, xpath, ymod, "netconf", xret)) < 0) - goto done; - if (ret == 0) - goto fail; - } - if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040")){ - if ((ymod = yang_find_module_by_name(yspec, "ietf-restconf-monitoring")) == NULL){ - clicon_err(OE_YANG, ENOENT, "yang module ietf-restconf-monitoring not found"); - goto done; - } - if ((namespace = yang_find_mynamespace(ymod)) == NULL){ - clicon_err(OE_YANG, ENOENT, "ietf-restconf-monitoring namespace not found"); - goto done; - } - cbuf_reset(cb); - /* XXX This code does not filter state data with xpath */ - cprintf(cb, "", namespace); - if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, xret, NULL) < 0) - goto done; - if ((ret = client_get_streams(h, yspec, xpath, ymod, "restconf-state", xret)) < 0) - goto done; - if (ret == 0) - goto fail; - if ((ret = client_get_capabilities(h, yspec, xpath, xret)) < 0) - goto done; - } - if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895")){ - if ((ret = yang_modules_state_get(h, yspec, xpath, nsc, 0, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - } - /* Use plugin state callbacks */ - if ((ret = clixon_plugin_statedata_all(h, yspec, nsc, xpath, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - retval = 1; /* OK */ - done: - clicon_debug(1, "%s %d", __FUNCTION__, retval); - if (cb) - cbuf_free(cb); - return retval; - fail: - retval = 0; - goto done; -} - -/*! Retrieve all or part of a specified configuration. - * - * Function reused from both from_client_get() and from_client_get_config - * @param[in] yspec - * @param[in] db - * @param[in] xpath - * @param[in] username - * @param[in] content - * @param[in] depth - * @param[out] cbret Return xml tree, eg ..., = (offset + limit)) - remaining = total - (offset + limit); - } - /* XXX need only first for remaining, but all for NACM */ - if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) - goto done; - /* Add remain attribute to first returning element */ - if (xlen){ - cxobj *x; - cxobj *xa; - x = xvec[0]; - if ((xa = xml_new("remaining", x, CX_ATTR)) == NULL) - goto done; - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cb, "%u", remaining); - if (xml_value_set(xa, cbuf_get(cb)) < 0) - goto done; - if (xml_prefix_set(xa, "cp") < 0) - goto done; - if (xmlns_set(x, "cp", "http://clicon.org/clixon-netconf-list-pagination") < 0) - goto done; - } -#endif - /* Pre-NACM access step */ - xnacm = clicon_nacm_cache(h); - if (xnacm != NULL){ /* Do NACM validation */ -#ifndef CLIXON_PAGINATION - if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) - goto done; -#endif - /* NACM datanode/module read validation */ - if (nacm_datanode_read(h, xret, xvec, xlen, username, xnacm) < 0) - goto done; - } - cprintf(cbret, "", NETCONF_BASE_NAMESPACE); - if (xret==NULL) - cprintf(cbret, ""); - else{ - if (xml_name_set(xret, NETCONF_OUTPUT_DATA) < 0) - goto done; - if (clicon_xml2cbuf(cbret, xret, 0, 0, depth>0?depth+1:depth) < 0) - goto done; - } - cprintf(cbret, ""); - ok: - retval = 0; - done: - if (cb) - cbuf_free(cb); - if (xerr) - xml_free(xerr); - if (xvec) - free(xvec); - if (xret) - xml_free(xret); - return retval; -} - -#if defined(LIST_PAGINATION) || defined(CLIXON_PAGINATION) -/*! Help function for parsing restconf query parameter and setting netconf attribute - * - * If not "unbounded", parse and set a numeric value - * @param[in] h Clixon handle - * @param[in] name Name of attribute - * @param[in] defaultstr Default string which is accepted and sets value to 0 - * @param[in,out] cbret Output buffer for internal RPC message - * @param[out] value Value - * @retval -1 Error - * @retval 0 Invalid, cbret set - * @retval 1 OK - */ -static int -element2value(clicon_handle h, - cxobj *xe, - char *name, - char *defaultstr, - cbuf *cbret, - uint32_t *value) -{ - int retval = -1; - char *valstr; - int ret; - char *reason = NULL; - cxobj *x; - - *value = 0; - if ((x = xml_find_type(xe, NULL, name, CX_ELMNT)) != NULL && - (valstr = xml_body(x)) != NULL && - strcmp(valstr, defaultstr) != 0){ - if ((ret = parse_uint32(valstr, value, &reason)) < 0){ - clicon_err(OE_XML, errno, "parse_uint32"); - goto done; - } - if (ret == 0){ - if (netconf_bad_element(cbret, "application", - name, "Unrecognized value") < 0) - goto done; - goto fail; - } - } - retval = 1; - done: - if (reason) - free(reason); - return retval; - fail: - retval = 0; - goto done; -} -#endif - -/*! Retrieve all or part of a specified configuration. - * - * @param[in] h Clicon handle - * @param[in] xe Request: - * @param[out] cbret Return xml tree, eg ..., - * The set of namespace declarations are those in scope on the - * element. - */ - else - if (xml_nsctx_node(xfilter, &nsc) < 0) - goto done; - if (xpath2canonical(xpath0, nsc, yspec, &xpath, &nsc1) < 0) - goto done; - if (nsc) - xml_nsctx_free(nsc); - nsc = nsc1; - } - /* Clixon extensions: depth */ - if ((attr = xml_find_value(xe, "depth")) != NULL){ - char *reason = NULL; - if ((ret = parse_int32(attr, &depth, &reason)) < 0){ - clicon_err(OE_XML, errno, "parse_int32"); - goto done; - } - if (ret == 0){ - if (netconf_bad_attribute(cbret, "application", - "depth", "Unrecognized value of depth attribute") < 0) - goto done; - goto ok; - } - } -#ifdef CLIXON_PAGINATION - /* limit */ - if ((ret = element2value(h, xe, "limit", "unbounded", cbret, &limit)) < 0) - goto done; - /* offset */ - if (ret && (ret = element2value(h, xe, "offset", "none", cbret, &offset)) < 0) - goto done; - /* direction */ - if (ret && (x = xml_find_type(xe, NULL, "direction", CX_ELMNT)) != NULL){ - direction = xml_body(x); - if (strcmp(direction, "forward") != 0 && strcmp(direction, "reverse") != 0){ - if (netconf_bad_attribute(cbret, "application", - "direction", "Unrecognized value of direction attribute") < 0) - goto done; - goto ok; - } - } - /* sort */ - if (ret && (x = xml_find_type(xe, NULL, "sort", CX_ELMNT)) != NULL) - sort = xml_body(x); - if (sort) ; /* XXX */ - /* where */ - if (ret && (x = xml_find_type(xe, NULL, "where", CX_ELMNT)) != NULL) - where = xml_body(x); - /* Build a "predicate" cbuf - * This solution uses xpath predicates to translate "limit" and "offset" to - * relational operators <>. - */ - if ((cbpath = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - /* This uses xpath. Maybe limit should use parameters */ - cprintf(cbpath, "%s", xpath); - if (where) - cprintf(cbpath, "[%s]", where); - if (offset){ - cprintf(cbpath, "[%u <= position()", offset); - if (limit) - cprintf(cbpath, " and position() < %u", limit+offset); - cprintf(cbpath, "]"); - } - else if (limit) - cprintf(cbpath, "[position() < %u]", limit); -#endif /* CLIXON_PAGINATION */ - if ((ret = client_get_config_only(h, nsc, yspec, db, xpath, -#ifdef CLIXON_PAGINATION - limit, offset, - cbuf_get(cbpath), -#endif - username, -1, cbret)) < 0) - goto done; - ok: - retval = 0; - done: - if (xpath) - free(xpath); - if (nsc) - xml_nsctx_free(nsc); - if (cbx) - cbuf_free(cbx); - return retval; -} - /*! Loads all or part of a specified configuration to target configuration * * @param[in] h Clicon handle @@ -863,7 +323,6 @@ from_client_edit_config(clicon_handle h, if (xml_sort_recurse(xc) < 0) goto done; if ((ret = xmldb_put(h, target, operation, xc, username, cbret)) < 0){ - clicon_debug(1, "%s ERROR PUT", __FUNCTION__); if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0) goto done; goto ok; @@ -940,13 +399,14 @@ from_client_copy_config(clicon_handle h, void *arg, void *regarg) { - int retval = -1; + int retval = -1; struct client_entry *ce = (struct client_entry *)arg; - char *source; - char *target; - uint32_t iddb; - uint32_t myid = ce->ce_id; - cbuf *cbx = NULL; /* Assist cbuf */ + char *source; + char *target; + uint32_t iddb; + uint32_t myid = ce->ce_id; + cbuf *cbx = NULL; /* Assist cbuf */ + cbuf *cbmsg = NULL; if ((source = netconf_db_find(xe, "source")) == NULL){ if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) @@ -983,7 +443,12 @@ from_client_copy_config(clicon_handle h, goto ok; } if (xmldb_copy(h, source, target) < 0){ - if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cbmsg, "Copy %s datastore to %s: %s", source, target, clicon_err_reason); + if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg))< 0) goto done; goto ok; } @@ -992,6 +457,8 @@ from_client_copy_config(clicon_handle h, ok: retval = 0; done: + if (cbmsg) + cbuf_free(cbmsg); if (cbx) cbuf_free(cbx); return retval; @@ -1019,6 +486,7 @@ from_client_delete_config(clicon_handle h, uint32_t iddb; uint32_t myid = ce->ce_id; cbuf *cbx = NULL; /* Assist cbuf */ + cbuf *cbmsg = NULL; /* XXX should use prefix cf edit_config */ if ((target = netconf_db_find(xe, "target")) == NULL || @@ -1046,12 +514,22 @@ from_client_delete_config(clicon_handle h, goto ok; } if (xmldb_delete(h, target) < 0){ - if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0) + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cbmsg, "Delete %s datastore: %s", target, clicon_err_reason); + if (netconf_operation_failed(cbret, "protocol", cbuf_get(cbmsg))< 0) goto done; goto ok; } if (xmldb_create(h, target) < 0){ - if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0) + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cbmsg, "Create %s datastore: %s", target, clicon_err_reason); + if (netconf_operation_failed(cbret, "protocol", cbuf_get(cbmsg))< 0) goto done; goto ok; } @@ -1060,6 +538,8 @@ from_client_delete_config(clicon_handle h, ok: retval = 0; done: + if (cbmsg) + cbuf_free(cbmsg); if (cbx) cbuf_free(cbx); return retval; @@ -1208,280 +688,6 @@ from_client_unlock(clicon_handle h, return retval; } -/*! Retrieve running configuration and device state information. - * - * @param[in] h Clicon handle - * @param[in] xe Request: - * @param[out] cbret Return xml tree, eg ..., - * The set of namespace declarations are those in scope on the - * element. - */ - else - if (xml_nsctx_node(xfilter, &nsc) < 0) - goto done; - if (xpath2canonical(xpath0, nsc, yspec, &xpath, &nsc1) < 0) - goto done; - if (nsc) - xml_nsctx_free(nsc); - nsc = nsc1; - } - /* Clixon extensions: content */ - if ((attr = xml_find_value(xe, "content")) != NULL) - content = netconf_content_str2int(attr); - /* Clixon extensions: depth */ - if ((attr = xml_find_value(xe, "depth")) != NULL){ - if ((ret = parse_int32(attr, &depth, &reason)) < 0){ - clicon_err(OE_XML, errno, "parse_int32"); - goto done; - } - if (ret == 0){ - if (netconf_bad_attribute(cbret, "application", - "depth", "Unrecognized value of depth attribute") < 0) - goto done; - goto ok; - } - } -#ifdef CLIXON_PAGINATION - /* limit */ - if ((ret = element2value(h, xe, "limit", "unbounded", cbret, &limit)) < 0) - goto done; - /* offset */ - if (ret && (ret = element2value(h, xe, "offset", "none", cbret, &offset)) < 0) - goto done; - /* direction */ - if (ret && (x = xml_find_type(xe, NULL, "direction", CX_ELMNT)) != NULL){ - direction = xml_body(x); - if (strcmp(direction, "forward") != 0 && strcmp(direction, "reverse") != 0){ - if (netconf_bad_attribute(cbret, "application", - "direction", "Unrecognized value of direction attribute") < 0) - goto done; - goto ok; - } - } - /* sort */ - if (ret && (x = xml_find_type(xe, NULL, "sort", CX_ELMNT)) != NULL) - sort = xml_body(x); - if (sort) ; /* XXX */ - /* where */ - if (ret && (x = xml_find_type(xe, NULL, "where", CX_ELMNT)) != NULL) - where = xml_body(x); - /* Build a "predicate" cbuf - * This solution uses xpath predicates to translate "limit" and "offset" to - * relational operators <>. - */ - if ((cbpath = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - /* This uses xpath. Maybe limit should use parameters */ - cprintf(cbpath, "%s", xpath); - if (where) - cprintf(cbpath, "[%s]", where); - if (offset){ - cprintf(cbpath, "[%u <= position()", offset); - if (limit) - cprintf(cbpath, " and position() < %u", limit+offset); - cprintf(cbpath, "]"); - } - else if (limit) - cprintf(cbpath, "[position() < %u]", limit); -#endif /* CLIXON_PAGINATION */ - if (content == CONTENT_CONFIG){ /* config only, no state */ - if (client_get_config_only(h, nsc, yspec, "running", xpath, -#ifdef CLIXON_PAGINATION - limit, offset, - cbuf_get(cbpath), -#endif - username, depth, cbret) < 0) - goto done; - goto ok; - } - /* If not only-state, then read running config - * Note xret can be pruned by nacm below and change name and - * merged with state data, so zero-copy cant be used - * Also, must use external namespace context here due to stmt - */ - if (clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){ - if (xmldb_get0(h, "running", YB_MODULE, nsc, NULL, 1, &xret, NULL, NULL) < 0) { - if (netconf_operation_failed(cbret, "application", "read registry")< 0) - goto done; - goto ok; - } - } - else{ - if (xmldb_get0(h, "running", YB_MODULE, nsc, xpath, 1, &xret, NULL, NULL) < 0) { - if (netconf_operation_failed(cbret, "application", "read registry")< 0) - goto done; - goto ok; - } - } - /* If not only config, - * get state data from plugins as defined by plugin_statedata(), if any - */ - clicon_err_reset(); - if ((ret = client_statedata(h, xpath?xpath:"/", nsc, content, &xret)) < 0) - goto done; - if (ret == 0){ /* Error from callback (error in xret) */ - if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0) - goto done; - goto ok; - } - if (clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){ - /* Check XML by validating it. return internal error with error cause - * Primarily intended for user-supplied state-data. - * The whole config tree must be present in case the state data references config data - */ - if ((ret = xml_yang_validate_all_top(h, xret, &xerr)) < 0) - goto done; - if (ret > 0 && - (ret = xml_yang_validate_add(h, xret, &xerr)) < 0) - goto done; - if (ret == 0){ - if (clicon_debug_get()) - clicon_log_xml(LOG_DEBUG, xret, "VALIDATE_STATE"); - if (clixon_netconf_internal_error(xerr, - ". Internal error, state callback returned invalid XML", - NULL) < 0) - goto done; - if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0) - goto done; - goto ok; - } - } /* CLICON_VALIDATE_STATE_XML */ - - if (content == CONTENT_NONCONFIG){ /* state only, all config should be removed now */ - /* Keep state data only, remove everything that is not config. Note that state data - * may be a sub-part in a config tree, we need to traverse to find all - */ - if (xml_non_config_data(xret, NULL) < 0) - goto done; - if (xml_tree_prune_flagged_sub(xret, XML_FLAG_MARK, 1, NULL) < 0) - goto done; - if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0) - goto done; - } - /* Code complex to filter out anything that is outside of xpath - * Actually this is a safety catch, should really be done in plugins - * and modules_state functions. - */ - if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) - goto done; - /* If vectors are specified then mark the nodes found and - * then filter out everything else, - * otherwise return complete tree. - */ - if (xvec != NULL){ - for (i=0; i", NETCONF_BASE_NAMESPACE); /* OK */ - if (xret==NULL) - cprintf(cbret, ""); - else{ - if (xml_name_set(xret, NETCONF_OUTPUT_DATA) < 0) - goto done; - /* Top level is data, so add 1 to depth if significant */ - if (clicon_xml2cbuf(cbret, xret, 0, 0, depth>0?depth+1:depth) < 0) - goto done; - } - cprintf(cbret, ""); - ok: - retval = 0; - done: - clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); - - if (cbpath) - cbuf_free(cbpath); - if (reason) - free(reason); - if (xerr) - xml_free(xerr); - if (xpath) - free(xpath); - if (xvec) - free(xvec); - if (nsc) - xml_nsctx_free(nsc); - if (xret) - xml_free(xret); - return retval; -} - /*! Request graceful termination of a NETCONF session. * @param[in] h Clicon handle * @param[in] xe Request: @@ -1564,321 +770,6 @@ from_client_kill_session(clicon_handle h, return retval; } -#ifdef LIST_PAGINATION - -/*! Retrieve collection configuration and device state information - * - * @param[in] h Clicon handle - * @param[in] xe Request: - * @param[out] cbret Return xml tree, eg ..., stuctured path tree */ - if ((ret = clixon_instance_id_parse(yspec, &path_tree, &xerr, "%s", xpath)) < 0) - goto done; - if (ret == 0){ - if (xerr && clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0) - goto done; - goto ok; - } - /* get last element of path, eg /a/b/c, get c */ - if ((cp = PREVQ(clixon_path *, path_tree)) == NULL){ - if (netconf_bad_element(cbret, "application", "list-target", "path invalid") < 0) - goto done; - goto ok; - } - /* get yang of last element */ - if ((y = cp->cp_yang) == NULL){ - if (netconf_bad_element(cbret, "application", "list-target", "No yang associated with path") < 0) - goto done; - goto ok; - } - if (yang_keyword_get(y) != Y_LIST && yang_keyword_get(y) != Y_LEAF_LIST){ - if (netconf_bad_element(cbret, "application", "list-target", "path invalid") < 0) - goto done; - goto ok; - } - /* Build a "predicate" cbuf - * This solution uses xpath predicates to translate "limit" and "offset" to - * relational operators <>. - */ - if ((cbpath = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - /* This uses xpath. Maybe limit should use parameters */ - cprintf(cbpath, "%s", xpath); - if (where) - cprintf(cbpath, "[%s]", where); - if (offset){ - cprintf(cbpath, "[%u <= position()", offset); - if (limit) - cprintf(cbpath, " and position() < %u", limit+offset); - cprintf(cbpath, "]"); - } - else if (limit) - cprintf(cbpath, "[position() < %u]", limit); - - /* Split into CT or CF */ - if (yang_config_ancestor(y) == 1){ /* CT */ - if (content == CONTENT_CONFIG || content == CONTENT_ALL){ - if ((ret = xmldb_get0(h, datastore, YB_MODULE, nsc, xpath, 1, &xret, NULL, &xerr)) < 0) { - if (netconf_operation_failed(cbret, "application", "read registry")< 0) - goto done; - goto ok; - } - if (ret == 0){ - if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0) - goto done; - goto ok; - } - /* First get number of hits (ALL entries: note not optimized) */ - if (xpath_count(xret, nsc, xpath, &total) < 0) - goto done; - if (total < (offset + limit)) - remaining = 0; - else - remaining = total - (offset + limit); - } - /* There may be CF data in a CT collection */ - if (content == CONTENT_ALL){ - if ((ret = client_statedata(h, cbuf_get(cbpath), nsc, content, &xret)) < 0) - goto done; - } - } - else { /* CF */ - /* There can be no CT data in a CF collection */ - if (content == CONTENT_NONCONFIG || content == CONTENT_ALL){ - if ((ret = client_statedata(h, cbuf_get(cbpath), nsc, content, &xret)) < 0) - goto done; - if (ret == 0){ /* Error from callback (error in xret) */ - if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0) - goto done; - goto ok; - } - } - } - if (0 && clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){ - /* XXX: subset in collection may not have mandatory variables */ - /* Check XML by validating it. return internal error with error cause - * Primarily intended for user-supplied state-data. - * The whole config tree must be present in case the state data references config data - */ - if ((ret = xml_yang_validate_all_top(h, xret, &xerr)) < 0) - goto done; - if (ret > 0 && - (ret = xml_yang_validate_add(h, xret, &xerr)) < 0) - goto done; - if (ret == 0){ - if (clicon_debug_get()) - clicon_log_xml(LOG_DEBUG, xret, "VALIDATE_STATE"); - if (clixon_netconf_internal_error(xerr, - ". Internal error, state callback returned invalid XML", - NULL) < 0) - goto done; - if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0) - goto done; - goto ok; - } - } /* CLICON_VALIDATE_STATE_XML */ - - if (content == CONTENT_NONCONFIG){ /* state only, all config should be removed now */ - /* Keep state data only, remove everything that is not config. Note that state data - * may be a sub-part in a config tree, we need to traverse to find all - */ - if (xml_non_config_data(xret, NULL) < 0) - goto done; - if (xml_tree_prune_flagged_sub(xret, XML_FLAG_MARK, 1, NULL) < 0) - goto done; - if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0) - goto done; - } - /* Code complex to filter out anything that is outside of xpath - * Actually this is a safety catch, should really be done in plugins - * and modules_state functions. - */ - if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, cbuf_get(cbpath)) < 0) - goto done; - - /* Pre-NACM access step */ - xnacm = clicon_nacm_cache(h); - if (xnacm != NULL){ /* Do NACM validation */ - /* NACM datanode/module read validation */ - if (nacm_datanode_read(h, xret, xvec, xlen, username, xnacm) < 0) - goto done; - } - cprintf(cbret, "", - NETCONF_BASE_NAMESPACE, NETCONF_COLLECTION_NAMESPACE); /* OK */ - if ((ns = yang_find_mynamespace(y)) != NULL) { - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - for (i=0; i0?depth+1:depth) < 0) - goto done; - } - } - cprintf(cbret, ""); - ok: - retval = 0; - done: - clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); - if (datastore) - free(datastore); - if (path_tree) - clixon_path_free(path_tree); - if (xtop) - xml_free(xtop); - if (cbpath) - cbuf_free(cbpath); - if (cb) - cbuf_free(cb); - if (reason) - free(reason); - if (xerr) - xml_free(xerr); - if (xvec) - free(xvec); - if (nsc) - xml_nsctx_free(nsc); - if (xret) - xml_free(xret); - return retval; -} -#endif /* LIST_PAGINATION */ /*! Create a notification subscription * @param[in] h Clicon handle diff --git a/apps/backend/backend_get.c b/apps/backend/backend_get.c new file mode 100644 index 00000000..57d36e2a --- /dev/null +++ b/apps/backend/backend_get.c @@ -0,0 +1,1108 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include + +#include "clixon_backend_handle.h" +#include "clixon_backend_plugin.h" +#include "clixon_backend_commit.h" +#include "backend_handle.h" +#include "backend_get.h" + +/*! + * Maybe should be in the restconf client instead of backend? + * @param[in] h Clicon handle + * @param[in] yspec Yang spec + * @param[in] xpath Xpath selection, not used but may be to filter early + * @param[out] xrs XML restconf-state node + * @see netconf_hello_server + * @see rfc8040 Sections 9.1 + */ +static int +client_get_capabilities(clicon_handle h, + yang_stmt *yspec, + char *xpath, + cxobj **xret) +{ + int retval = -1; + cxobj *xrstate = NULL; /* xml restconf-state node */ + cbuf *cb = NULL; + + if ((xrstate = xpath_first(*xret, NULL, "restconf-state")) == NULL){ + clicon_err(OE_YANG, ENOENT, "restconf-state not found in config node"); + goto done; + } + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, ""); + cprintf(cb, "urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit"); + cprintf(cb, "urn:ietf:params:restconf:capability:depth:1.0"); + cprintf(cb, ""); + if (clixon_xml_parse_string(cbuf_get(cb), YB_PARENT, NULL, &xrstate, NULL) < 0) + goto done; + retval = 0; + done: + if (cb) + cbuf_free(cb); + return retval; +} + +/*! Get streams state according to RFC 8040 or RFC5277 common function + * @param[in] h Clicon handle + * @param[in] yspec Yang spec + * @param[in] xpath Xpath selection, not used but may be to filter early + * @param[in] module Name of yang module + * @param[in] top Top symbol, ie netconf or restconf-state + * @param[in,out] xret Existing XML tree, merge x into this + * @retval -1 Error (fatal) + * @retval 0 Statedata callback failed + * @retval 1 OK + */ +static int +client_get_streams(clicon_handle h, + yang_stmt *yspec, + char *xpath, + yang_stmt *ymod, + char *top, + cxobj **xret) +{ + int retval = -1; + yang_stmt *yns = NULL; /* yang namespace */ + cxobj *x = NULL; + cbuf *cb = NULL; + int ret; + + if ((yns = yang_find(ymod, Y_NAMESPACE, NULL)) == NULL){ + clicon_err(OE_YANG, 0, "%s yang namespace not found", yang_argument_get(ymod)); + goto done; + } + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "<%s xmlns=\"%s\">", top, yang_argument_get(yns)); + /* Second argument is a hack to have the same function for the + * RFC5277 and 8040 stream cases + */ + if (stream_get_xml(h, strcmp(top,"restconf-state")==0, cb) < 0) + goto done; + cprintf(cb,"", top); + + if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x, NULL) < 0){ + if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) + goto done; + goto fail; + } + if ((ret = netconf_trymerge(x, yspec, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + retval = 1; + done: + if (cb) + cbuf_free(cb); + if (x) + xml_free(x); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Get system state-data, including streams and plugins + * @param[in] h Clicon handle + * @param[in] xpath XPath selection, may be used to filter early + * @param[in] nsc XML Namespace context for xpath + * @param[in,out] xret Existing XML tree, merge x into this + * @retval -1 Error (fatal) + * @retval 0 Statedata callback failed (clicon_err called) + * @retval 1 OK + */ +static int +client_statedata(clicon_handle h, + char *xpath, + cvec *nsc, + cxobj **xret) +{ + int retval = -1; + yang_stmt *yspec; + yang_stmt *ymod; + int ret; + char *namespace; + cbuf *cb = NULL; + + clicon_debug(1, "%s", __FUNCTION__); + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_YANG, ENOENT, "No yang spec"); + goto done; + } + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + /* Add default state to config if present */ + if (xml_default_recurse(*xret, 1) < 0) + goto done; + /* Add default global state */ + if (xml_global_defaults(h, *xret, nsc, xpath, yspec, 1) < 0) + goto done; + + if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277")){ + if ((ymod = yang_find_module_by_name(yspec, "clixon-rfc5277")) == NULL){ + clicon_err(OE_YANG, ENOENT, "yang module clixon-rfc5277 not found"); + goto done; + } + if ((namespace = yang_find_mynamespace(ymod)) == NULL){ + clicon_err(OE_YANG, ENOENT, "clixon-rfc5277 namespace not found"); + goto done; + } + cprintf(cb, "", namespace); + if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, xret, NULL) < 0) + goto done; + if ((ret = client_get_streams(h, yspec, xpath, ymod, "netconf", xret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040")){ + if ((ymod = yang_find_module_by_name(yspec, "ietf-restconf-monitoring")) == NULL){ + clicon_err(OE_YANG, ENOENT, "yang module ietf-restconf-monitoring not found"); + goto done; + } + if ((namespace = yang_find_mynamespace(ymod)) == NULL){ + clicon_err(OE_YANG, ENOENT, "ietf-restconf-monitoring namespace not found"); + goto done; + } + cbuf_reset(cb); + /* XXX This code does not filter state data with xpath */ + cprintf(cb, "", namespace); + if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, xret, NULL) < 0) + goto done; + if ((ret = client_get_streams(h, yspec, xpath, ymod, "restconf-state", xret)) < 0) + goto done; + if (ret == 0) + goto fail; + if ((ret = client_get_capabilities(h, yspec, xpath, xret)) < 0) + goto done; + } + if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895")){ + if ((ret = yang_modules_state_get(h, yspec, xpath, nsc, 0, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + /* Use plugin state callbacks */ + if ((ret = clixon_plugin_statedata_all(h, yspec, nsc, xpath, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + retval = 1; /* OK */ + done: + clicon_debug(1, "%s %d", __FUNCTION__, retval); + if (cb) + cbuf_free(cb); + return retval; + fail: + retval = 0; + goto done; +} + +#if defined(LIST_PAGINATION) || defined(CLIXON_PAGINATION) +/*! Help function for parsing restconf query parameter and setting netconf attribute + * + * If not "unbounded", parse and set a numeric value + * @param[in] h Clixon handle + * @param[in] name Name of attribute + * @param[in] defaultstr Default string which is accepted and sets value to 0 + * @param[in,out] cbret Output buffer for internal RPC message + * @param[out] value Value + * @retval -1 Error + * @retval 0 Invalid, cbret set + * @retval 1 OK + */ +static int +element2value(clicon_handle h, + cxobj *xe, + char *name, + char *defaultstr, + cbuf *cbret, + uint32_t *value) +{ + int retval = -1; + char *valstr; + int ret; + char *reason = NULL; + cxobj *x; + + *value = 0; + if ((x = xml_find_type(xe, NULL, name, CX_ELMNT)) != NULL && + (valstr = xml_body(x)) != NULL && + strcmp(valstr, defaultstr) != 0){ + if ((ret = parse_uint32(valstr, value, &reason)) < 0){ + clicon_err(OE_XML, errno, "parse_uint32"); + goto done; + } + if (ret == 0){ + if (netconf_bad_element(cbret, "application", + name, "Unrecognized value") < 0) + goto done; + goto fail; + } + } + retval = 1; + done: + if (reason) + free(reason); + return retval; + fail: + retval = 0; + goto done; +} +#endif + +/*! Common get/get-config code for retrieving configuration and state information. + * + * @param[in] h Clicon handle + * @param[in] xe Request: + * @param[in] content Get config/state/both + * @param[in] db Database name + * @param[out] cbret Return xml tree, eg ..., + * The set of namespace declarations are those in scope on the + * element. + */ + else + if (xml_nsctx_node(xfilter, &nsc) < 0) + goto done; + if (xpath2canonical(xpath0, nsc, yspec, &xpath, &nsc1) < 0) + goto done; + if (nsc) + xml_nsctx_free(nsc); + nsc = nsc1; + } + /* Clixon extensions: depth */ + if ((attr = xml_find_value(xe, "depth")) != NULL){ + if ((ret = parse_int32(attr, &depth, &reason)) < 0){ + clicon_err(OE_XML, errno, "parse_int32"); + goto done; + } + if (ret == 0){ + if (netconf_bad_attribute(cbret, "application", + "depth", "Unrecognized value of depth attribute") < 0) + goto done; + goto ok; + } + } +#ifdef CLIXON_PAGINATION + /* Check if list pagination */ + if ((x = xml_find_type(xe, NULL, "list-pagination", CX_ELMNT)) != NULL && + (valstr = xml_body(x)) != NULL && + strcmp(valstr,"true")==0) + list_pagination = 1; + /* Sanity check for list pagination: path must be a list/leaf-list, if it is, + * check config/state + */ + if (list_pagination){ + /* Check if list/leaf-list */ + if (yang_path_arg(yspec, xpath, &ylist) < 0) + goto done; + if (ylist == NULL){ + if (netconf_invalid_value_xml(&xerr, "application", "list-pagination is enabled but target is not found") < 0) + goto done; + goto ok; + } + if (yang_keyword_get(ylist) != Y_LIST && + yang_keyword_get(ylist) != Y_LEAF_LIST){ + if (netconf_invalid_value_xml(&xerr, "application", "list-pagination is enabled but target is not leaf or leaf-list") < 0) + goto done; + goto ok; + } + if ((list_config = yang_config_ancestor(ylist)) != 0){ /* config list */ + if (content == CONTENT_NONCONFIG){ + if (netconf_invalid_value_xml(&xerr, "application", "list-pagination targets a config list but content request is nonconfig") < 0) + goto done; + goto ok; + } + } + else { /* state list */ + if (content == CONTENT_CONFIG){ + if (netconf_invalid_value_xml(&xerr, "application", "list-pagination targets a state list but content request is config") < 0) + goto done; + goto ok; + } + } + /* limit */ + if ((ret = element2value(h, xe, "limit", "unbounded", cbret, &limit)) < 0) + goto done; + /* offset */ + if (ret && (ret = element2value(h, xe, "offset", "none", cbret, &offset)) < 0) + goto done; + /* direction */ + if (ret && (x = xml_find_type(xe, NULL, "direction", CX_ELMNT)) != NULL){ + direction = xml_body(x); + if (strcmp(direction, "forward") != 0 && strcmp(direction, "reverse") != 0){ + if (netconf_bad_attribute(cbret, "application", + "direction", "Unrecognized value of direction attribute") < 0) + goto done; + goto ok; + } + } + /* sort */ + if (ret && (x = xml_find_type(xe, NULL, "sort", CX_ELMNT)) != NULL) + sort = xml_body(x); + if (sort) ; /* XXX */ + /* where */ + if (ret && (x = xml_find_type(xe, NULL, "where", CX_ELMNT)) != NULL) + where = xml_body(x); + /* Build a "predicate" cbuf + * This solution uses xpath predicates to translate "limit" and "offset" to + * relational operators <>. + */ + if ((cbpath = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + /* This uses xpath. Maybe limit should use parameters */ + if (xpath) + cprintf(cbpath, "%s", xpath); + else + cprintf(cbpath, "/"); + if (where) + cprintf(cbpath, "[%s]", where); + if (offset){ + cprintf(cbpath, "[%u <= position()", offset); + if (limit) + cprintf(cbpath, " and position() < %u", limit+offset); + cprintf(cbpath, "]"); + } + else if (limit) + cprintf(cbpath, "[position() < %u]", limit); + /* Get total/remaining + * XXX: Maybe together with get config / state data + */ + if (list_config){ + if ((xcache = xmldb_cache_get(h, db)) != NULL){ + if (xpath_count(xcache, nsc, xpath, &total) < 0) + goto done; + if (total >= (offset + limit)) + remaining = total - (offset + limit); + } + } + else{ + /* XXX remaining of state list??*/ + } + /* Append predicate to original xpath and replace it */ + if (xpath) + free(xpath); + if ((xpath = strdup(cbuf_get(cbpath))) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + + } /* list_pagination */ +#endif /* CLIXON_PAGINATION */ + /* Read config + * XXX This seems unnecessary complex + */ + switch (content){ + case CONTENT_CONFIG: /* config data only */ + /* specific xpath */ + if (xmldb_get0(h, db, YB_MODULE, nsc, xpath?xpath:"/", 1, &xret, NULL, NULL) < 0) { + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cbmsg, "Get %s datastore: %s", db, clicon_err_reason); + if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg)) < 0) + goto done; + goto ok; + } + break; + case CONTENT_ALL: /* both config and state */ + case CONTENT_NONCONFIG: /* state data only */ + if (clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){ + /* Whole config tree, for validate debug */ + if (xmldb_get0(h, "running", YB_MODULE, nsc, NULL, 1, &xret, NULL, NULL) < 0) { + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cbmsg, "Get %s datastore: %s", db, clicon_err_reason); + if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg)) < 0) + goto done; + goto ok; + } + } + else if (content == CONTENT_ALL){ + /* specific xpath */ + if (xmldb_get0(h, db, YB_MODULE, nsc, xpath?xpath:"/", 1, &xret, NULL, NULL) < 0) { + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cbmsg, "Get %s datastore: %s", db, clicon_err_reason); + if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg)) < 0) + goto done; + goto ok; + } + } + /* CONTENT_NONCONFIG */ + else if ((xret = xml_new(DATASTORE_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)/* Only top tree */ + goto done; + break; + }/* switch content */ + /* If not only config, + * get state data from plugins as defined by plugin_statedata(), if any + */ + /* Read state XXX can we do this first and create an operational db? */ + switch (content){ + case CONTENT_CONFIG: /* config data only */ + break; + case CONTENT_ALL: /* both config and state */ + case CONTENT_NONCONFIG: /* state data only */ + if ((ret = client_statedata(h, xpath?xpath:"/", nsc, &xret)) < 0) + goto done; + if (ret == 0){ /* Error from callback (error in xret) */ + if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0) + goto done; + goto ok; + } + break; + } + + if (content != CONTENT_CONFIG && + clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){ + /* Check XML by validating it. return internal error with error cause + * Primarily intended for user-supplied state-data. + * The whole config tree must be present in case the state data references config data + */ + if ((ret = xml_yang_validate_all_top(h, xret, &xerr)) < 0) + goto done; + if (ret > 0 && + (ret = xml_yang_validate_add(h, xret, &xerr)) < 0) + goto done; + if (ret == 0){ + if (clicon_debug_get()) + clicon_log_xml(LOG_DEBUG, xret, "VALIDATE_STATE"); + if (clixon_netconf_internal_error(xerr, + ". Internal error, state callback returned invalid XML", + NULL) < 0) + goto done; + if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0) + goto done; + goto ok; + } + } /* CLICON_VALIDATE_STATE_XML */ + + if (content == CONTENT_NONCONFIG){ /* state only, all config should be removed now */ + /* Keep state data only, remove everything that is config. Note that state data + * may be a sub-part in a config tree, we need to traverse to find all + */ + if (xml_non_config_data(xret, NULL) < 0) + goto done; + if (xml_tree_prune_flagged_sub(xret, XML_FLAG_MARK, 1, NULL) < 0) + goto done; + if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0) + goto done; + } + /* Code complex to filter out anything that is outside of xpath + * Actually this is a safety catch, should really be done in plugins + * and modules_state functions. + * But it is problematic, because defaults, at least of config data, is in place + * and we need to re-add it. + */ + if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) + goto done; + + /* If vectors are specified then mark the nodes found and + * then filter out everything else, + * otherwise return complete tree. + */ + if (xvec != NULL){ + for (i=0; i", NETCONF_BASE_NAMESPACE); /* OK */ + if (xret==NULL) + cprintf(cbret, ""); + else{ + if (xml_name_set(xret, NETCONF_OUTPUT_DATA) < 0) + goto done; + /* Top level is data, so add 1 to depth if significant */ + if (clicon_xml2cbuf(cbret, xret, 0, 0, depth>0?depth+1:depth) < 0) + goto done; + } + cprintf(cbret, ""); + ok: + retval = 0; + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); +#ifdef CLIXON_PAGINATION + if (cbpath) + cbuf_free(cbpath); +#endif + if (cbmsg) + cbuf_free(cbmsg); + if (reason) + free(reason); + if (xerr) + xml_free(xerr); + if (xpath) + free(xpath); + if (xvec) + free(xvec); + if (xvecnacm) + free(xvecnacm); + if (nsc) + xml_nsctx_free(nsc); + if (xret) + xml_free(xret); + return retval; +} + +/*! Retrieve all or part of a specified configuration. + * + * @param[in] h Clicon handle + * @param[in] xe Request: + * @param[out] cbret Return xml tree, eg ..., + * @param[out] cbret Return xml tree, eg ..., + * @param[out] cbret Return xml tree, eg ..., stuctured path tree */ + if ((ret = clixon_instance_id_parse(yspec, &path_tree, &xerr, "%s", xpath)) < 0) + goto done; + if (ret == 0){ + if (xerr && clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0) + goto done; + goto ok; + } + /* get last element of path, eg /a/b/c, get c */ + if ((cp = PREVQ(clixon_path *, path_tree)) == NULL){ + if (netconf_bad_element(cbret, "application", "list-target", "path invalid") < 0) + goto done; + goto ok; + } + /* get yang of last element */ + if ((y = cp->cp_yang) == NULL){ + if (netconf_bad_element(cbret, "application", "list-target", "No yang associated with path") < 0) + goto done; + goto ok; + } + if (yang_keyword_get(y) != Y_LIST && yang_keyword_get(y) != Y_LEAF_LIST){ + if (netconf_bad_element(cbret, "application", "list-target", "path invalid") < 0) + goto done; + goto ok; + } + /* Build a "predicate" cbuf + * This solution uses xpath predicates to translate "limit" and "offset" to + * relational operators <>. + */ + if ((cbpath = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + /* This uses xpath. Maybe limit should use parameters */ + cprintf(cbpath, "%s", xpath); + if (where) + cprintf(cbpath, "[%s]", where); + if (offset){ + cprintf(cbpath, "[%u <= position()", offset); + if (limit) + cprintf(cbpath, " and position() < %u", limit+offset); + cprintf(cbpath, "]"); + } + else if (limit) + cprintf(cbpath, "[position() < %u]", limit); + + /* Split into CT or CF */ + if (yang_config_ancestor(y) == 1){ /* CT */ + if (content == CONTENT_CONFIG || content == CONTENT_ALL){ + if ((ret = xmldb_get0(h, datastore, YB_MODULE, nsc, xpath, 1, &xret, NULL, &xerr)) < 0) { + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; + goto ok; + } + if (ret == 0){ + if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0) + goto done; + goto ok; + } + /* First get number of hits (ALL entries: note not optimized) */ + if (xpath_count(xret, nsc, xpath, &total) < 0) + goto done; + if (total < (offset + limit)) + remaining = 0; + else + remaining = total - (offset + limit); + } + /* There may be CF data in a CT collection */ + if (content == CONTENT_ALL){ + if ((ret = client_statedata(h, cbuf_get(cbpath), nsc, &xret)) < 0) + goto done; + } + } + else { /* CF */ + /* There can be no CT data in a CF collection */ + if (content == CONTENT_NONCONFIG || content == CONTENT_ALL){ + if ((ret = client_statedata(h, cbuf_get(cbpath), nsc, &xret)) < 0) + goto done; + if (ret == 0){ /* Error from callback (error in xret) */ + if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0) + goto done; + goto ok; + } + } + } + if (0 && clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){ + /* XXX: subset in collection may not have mandatory variables */ + /* Check XML by validating it. return internal error with error cause + * Primarily intended for user-supplied state-data. + * The whole config tree must be present in case the state data references config data + */ + if ((ret = xml_yang_validate_all_top(h, xret, &xerr)) < 0) + goto done; + if (ret > 0 && + (ret = xml_yang_validate_add(h, xret, &xerr)) < 0) + goto done; + if (ret == 0){ + if (clicon_debug_get()) + clicon_log_xml(LOG_DEBUG, xret, "VALIDATE_STATE"); + if (clixon_netconf_internal_error(xerr, + ". Internal error, state callback returned invalid XML", + NULL) < 0) + goto done; + if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0) + goto done; + goto ok; + } + } /* CLICON_VALIDATE_STATE_XML */ + + if (content == CONTENT_NONCONFIG){ /* state only, all config should be removed now */ + /* Keep state data only, remove everything that is not config. Note that state data + * may be a sub-part in a config tree, we need to traverse to find all + */ + if (xml_non_config_data(xret, NULL) < 0) + goto done; + if (xml_tree_prune_flagged_sub(xret, XML_FLAG_MARK, 1, NULL) < 0) + goto done; + if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0) + goto done; + } + /* Code complex to filter out anything that is outside of xpath + * Actually this is a safety catch, should really be done in plugins + * and modules_state functions. + */ + if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, cbuf_get(cbpath)) < 0) + goto done; + + /* Pre-NACM access step */ + xnacm = clicon_nacm_cache(h); + if (xnacm != NULL){ /* Do NACM validation */ + /* NACM datanode/module read validation */ + if (nacm_datanode_read(h, xret, xvec, xlen, username, xnacm) < 0) + goto done; + } + cprintf(cbret, "", + NETCONF_BASE_NAMESPACE, NETCONF_COLLECTION_NAMESPACE); /* OK */ + if ((ns = yang_find_mynamespace(y)) != NULL) { + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + for (i=0; i0?depth+1:depth) < 0) + goto done; + } + } + cprintf(cbret, ""); + ok: + retval = 0; + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (datastore) + free(datastore); + if (path_tree) + clixon_path_free(path_tree); + if (xtop) + xml_free(xtop); + if (cbpath) + cbuf_free(cbpath); + if (cb) + cbuf_free(cb); + if (reason) + free(reason); + if (xerr) + xml_free(xerr); + if (xvec) + free(xvec); + if (nsc) + xml_nsctx_free(nsc); + if (xret) + xml_free(xret); + return retval; +} +#endif /* LIST_PAGINATION */ diff --git a/apps/backend/backend_get.h b/apps/backend/backend_get.h new file mode 100644 index 00000000..ea2ad755 --- /dev/null +++ b/apps/backend/backend_get.h @@ -0,0 +1,47 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC (Netgate) + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + */ + +#ifndef _BACKEND_GET_H_ +#define _BACKEND_GET_H_ + +/* + * Prototypes + */ +int from_client_get_config(clicon_handle h, cxobj *xe, cbuf *cbret, void *arg, void *regarg); +int from_client_get(clicon_handle h, cxobj *xe, cbuf *cbret, void *arg, void *regarg); +int from_client_get_pageable_list(clicon_handle h, cxobj *xe, cbuf *cbret, void *arg, void *regarg); /* XXX */ + +#endif /* _BACKEND_GET_H_ */ diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index b4b251ae..d4745aab 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -688,6 +688,7 @@ xml_diff(yang_stmt *yspec, } /*! 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 @@ -701,7 +702,6 @@ xml_diff(yang_stmt *yspec, * @note This function seems a little too complex semantics * @see xml_tree_prune_flagged for a simpler variant */ -#if 1 int xml_tree_prune_flagged_sub(cxobj *xt, int flag, @@ -773,97 +773,6 @@ xml_tree_prune_flagged_sub(cxobj *xt, *upmark = mark; return retval; } -#else -/* This is optimized in the sense that xml_purge is replaced with xml_child_rm but it leaks memory, - * in poarticualr attributes and namespace caches - */ -int -xml_tree_prune_flagged_sub(cxobj *xt, - int flag, - int test, - int *upmark) -{ - int retval = -1; - int submark; - int mark; - cxobj *x; - cxobj *xprev; - int iskey; - int anykey=0; - yang_stmt *yt; - int i; - - mark = 0; - yt = xml_spec(xt); /* xan be null */ - x = NULL; - xprev = x = NULL; - i = 0; - while ((x = xml_child_each(xt, x, -1)) != NULL) { - i++; - if (xml_type(x) != CX_ELMNT){ - xprev = x; - continue; - } - if (xml_flag(x, flag) == test?flag:0){ - /* Pass test */ - mark++; - xprev = x; - continue; /* mark and stop here */ - } - /* If it is key dont remove it yet (see second round) */ - if (yt){ - if ((iskey = yang_key_match(yt, xml_name(x))) < 0) - goto done; - if (iskey){ - anykey++; - xprev = x; /* skip if this is key */ - continue; - } - } - 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 - */ - if (submark) - mark++; - else{ /* Safe with xml_child_each if last */ - if (xml_child_rm(xt, i-1) < 0) - goto done; - i--; - x = xprev; - } - xprev = x; - } - /* Second round: if any keys were found, and no marks detected, purge now */ - if (anykey && !mark){ - x = NULL; - xprev = x = NULL; - i = 0; - while ((x = xml_child_each(xt, x, -1)) != NULL) { - i++; - if (xml_type(x) != CX_ELMNT){ - xprev = x; - continue; - } - /* If it is key remove it here */ - if (yt){ - if ((iskey = yang_key_match(yt, xml_name(x))) < 0) - goto done; - if (xml_child_rm(xt, i-1) < 0) - goto done; - i--; - x = xprev; - } - xprev = x; - } - } - retval = 0; - done: - if (upmark) - *upmark = mark; - return retval; -} -#endif /*! Prune everything that passes test * @param[in] xt XML tree with some node marked diff --git a/lib/src/clixon_xpath_yang.c b/lib/src/clixon_xpath_yang.c index f2638fb8..3355c445 100644 --- a/lib/src/clixon_xpath_yang.c +++ b/lib/src/clixon_xpath_yang.c @@ -150,6 +150,7 @@ xp_yang_eval_step(xp_yang_ctx *xy0, char *prefix; yang_stmt *ys; xp_yang_ctx *xy = NULL; + yang_stmt *ys1 = NULL; /* Create new xy */ if ((xy = xy_dup(xy0)) == NULL) @@ -164,9 +165,12 @@ xp_yang_eval_step(xp_yang_ctx *xy0, switch (nodetest->xs_type){ case XP_NODE: if ((prefix = nodetest->xs_s0) != NULL){ - if (yang_keyword_get(ys) == Y_MODULE){ /* This means top */ - yang_stmt *ys1 = NULL; - /* XXX: Kludge with prefixes */ + /* XXX: Kludge with prefixes */ + if (yang_keyword_get(ys) == Y_SPEC){ /* This means top */ + if ((ys1 = yang_find_module_by_prefix_yspec(ys, prefix)) != NULL) + ys = ys1; + } + else if (yang_keyword_get(ys) == Y_MODULE){ /* This means top */ if ((ys1 = yang_find_module_by_prefix(ys, prefix)) == NULL) ys1 = yang_find_module_by_prefix_yspec(ys_spec(ys), prefix); if (ys1 != NULL) @@ -323,9 +327,15 @@ xp_yang_eval(xp_yang_ctx *xy, } } break; + case XP_PRIME_STR: + if ((*xyr = xy_dup(xy)) == NULL) + goto done; + goto ok; + break; case XP_ABSPATH: /* Set context node to top node, and nodeset to that node only */ - xy->xy_node = ys_module(xy->xy_node); + if (yang_keyword_get(xy->xy_node) != Y_SPEC) + xy->xy_node = ys_module(xy->xy_node); break; case XP_PRED: if (xp_yang_eval_predicate(xy, xptree, xyr) < 0) @@ -419,15 +429,24 @@ xp_yang_eval(xp_yang_ctx *xy, * Leafrefs have a path arguments that are used both for finding referred XML node instances as well * as finding a referred YANG node for typechecks. * Such a path-arg is defined as: - * The syntax for a path argument is a subset of the XPath abbreviated + * The syntax for a path argument is a subset of the XPath abbreviated * syntax. Predicates are used only for constraining the values for the * key nodes for list entries. Each predicate consists of exactly one * equality test per key, and multiple adjacent predicates MAY be * present if a list has multiple keys. - * @param[in] ys YANG referring leaf node - * @param[in] path_arg Leafref path-arg + * @param[in] ys YANG referring node + * @param[in] path_arg path-arg * @param[out] yref YANG referred node * @note this function uses XPATH parser, which is (much too) general + * @code + * yang_stmt *ys; // source / referring node + * yang_stmt *yref = NULL; // target / referred node + * char *path_arg="../config/name"; + * + * if (yang_path_arg(ys, path_arg, &yref) < 0) + * err; + * @endcode + * @see rfc7950 Sec 9.9.2 * @see rfc7950 Sec 14 (leafref path) */ diff --git a/test/test_augment_state.sh b/test/test_augment_state.sh index 29e52c4a..eb8576a1 100755 --- a/test/test_augment_state.sh +++ b/test/test_augment_state.sh @@ -189,6 +189,7 @@ cat < $fstate EOF +# Note Expect gbds(default) + gbos(optional), the latter given by file above EXPSTATE=$(cat <gbdsgbosgadsgaos EOF diff --git a/test/test_leafref_state.sh b/test/test_leafref_state.sh index 990e4878..50325d2d 100755 --- a/test/test_leafref_state.sh +++ b/test/test_leafref_state.sh @@ -10,7 +10,10 @@ # with different paths. # Using the -sS state capability of the main example, that is why CLICON_BACKEND_DIR is # /usr/local/lib/$APPNAME/backend so that the main backend plugins is included. -# These tests require VALIDATE_STATE_XML to be set +# Note: Three runs: +# 1. with state data validation and with require-instance (Invalid) +# 2. with state data validation and without require-instance (OK) +# 3. without state data validation and with require-instance (Wrong state data no detected) # Magic line must be first in script (see README.md) s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi @@ -20,6 +23,7 @@ APPNAME=example cfg=$dir/conf_yang.xml fstate=$dir/state.xml fyang=$dir/leafref.yang +fyangno=$dir/leafrefno.yang # No require-instance cat < $cfg @@ -66,6 +70,32 @@ module leafref{ } EOF +# No require-instance in leafref +cat < $fyangno +module leafref{ + yang-version 1.1; + namespace "urn:example:example"; + prefix ex; + list sender-config{ + description "Main config of senders"; + key name; + leaf name{ + type string; + } + } + list sender-state{ + description "State referencing configured senders"; + config false; + key ref; + leaf ref{ + type leafref { + path "/ex:sender-config/ex:name"; + } + } + } +} +EOF + # This is state data written to file that backend reads from (on request) cat < $fstate @@ -73,6 +103,7 @@ cat < $fstate EOF +# First run: With validation of state callbacks new "test params: -f $cfg -- -sS $fstate" if [ $BE -ne 0 ]; then @@ -167,7 +198,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" -# Leafref wrong +# Leafref wrong internal: state references x but config contains only y new "netconf get / config+state should fail" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name in leafref.yang:[0-9]*. Internal error, state callback returned invalid XML]]>]]>$" @@ -188,6 +219,117 @@ if [ $BE -ne 0 ]; then stop_backend -f $cfg fi +# Second run: Validation and no require-instance +new "Second run: -f $cfg -o CLICON_YANG_MAIN_FILE=$fyangno -- -sS $fstate" + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -o CLICON_VALIDATE_STATE_XML=false -- -sS $fstate" + start_backend -s init -f $cfg -o CLICON_YANG_MAIN_FILE=$fyangno -- -sS $fstate +fi + +new "wait backend" +wait_backend + +# Add y +XML=$(cat < + y + +EOF +) + +# Reference (non-existing) x +cat < $fstate + + x + +EOF + +new "leafref config sender x" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO$XML]]>]]>" "^]]>]]>$" + +new "netconf commit" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" + +# Leafref wrong internal: state references x but config contains only y +new "netconf get / config+state wrong state xml but no validation" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "yx]]>]]>$" + +new "netconf get / state-only wrong state xml but no validation" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^x]]>]]>$" + +if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg +fi + +# Third run: No validation of state callbacks +new "Third run: -f $cfg -o CLICON_VALIDATE_STATE_XML=true -- -sS $fstate" + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -o CLICON_VALIDATE_STATE_XML=false -- -sS $fstate" + start_backend -s init -f $cfg -o CLICON_VALIDATE_STATE_XML=false -- -sS $fstate +fi + +new "wait backend" +wait_backend + +# Add y +XML=$(cat < + y + +EOF +) + +# Reference (non-existing) x +cat < $fstate + + x + +EOF + +new "leafref config sender x" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO$XML]]>]]>" "^]]>]]>$" + +new "netconf commit" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" + +# Leafref wrong internal: state references x but config contains only y +new "netconf get / config+state wrong state xml but no validation" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "yx]]>]]>$" + +new "netconf get / state-only wrong state xml but no validation" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^x]]>]]>$" + +if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg +fi + + rm -rf $dir new "endtest" diff --git a/test/test_pagination.sh b/test/test_pagination.sh index 81bcf064..66ac0ecf 100755 --- a/test/test_pagination.sh +++ b/test/test_pagination.sh @@ -20,6 +20,9 @@ fstate=$dir/mystate.xml # Define default restconfig config: RESTCONFIG RESTCONFIG=$(restconf_config none false) +# Validate internal state xml +: ${validatexml:=false} + cat < $cfg $cfg @@ -38,7 +41,7 @@ cat < $cfg $APPNAME /usr/local/lib/$APPNAME/cli /usr/local/lib/$APPNAME/clispec - false + $validatexml $RESTCONFIG EOF @@ -269,10 +272,10 @@ function testlimit() # "clixon get" new "clixon limit=$limit NETCONF get-config" - expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO$limit]]>]]>" "alicepublic17]]>]]>$" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOtrue$limit]]>]]>" "alicepublic17]]>]]>$" new "clixon limit=$limit NETCONF get" -# expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO$limit]]>]]>" "alicepublic17]]>]]>$" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOtrue$limit]]>]]>" "alicepublic17]]>]]>$" # "old: ietf" new "ietf limit=$limit NETCONF" @@ -346,6 +349,7 @@ if [ $BE -ne 0 ]; then fi unset RESTCONFIG +unset validatexml rm -rf $dir diff --git a/test/test_upgrade_failsafe.sh b/test/test_upgrade_failsafe.sh index 2a664627..bc6faf91 100755 --- a/test/test_upgrade_failsafe.sh +++ b/test/test_upgrade_failsafe.sh @@ -361,7 +361,7 @@ if [ $valgrindtest -ne 2 ]; then new "8. Load non-compat startup. Syntax fail, enter failsafe, startup invalid" (cd $dir; rm -f tmp_db candidate_db running_db startup_db) # remove databases (cd $dir; cp compat-err.xml startup_db) -runtest true startup 'always work' 'applicationoperation-failederrorread registry' +runtest true startup 'always work' 'applicationoperation-failederrorGet startup datastore: xml_parse: line 14: syntax error: at or before: <' fi # valgrindtest rm -rf $dir diff --git a/yang/clixon/clixon-netconf-list-pagination@2021-08-27.yang b/yang/clixon/clixon-netconf-list-pagination@2021-08-27.yang index d64e5251..8730da6f 100644 --- a/yang/clixon/clixon-netconf-list-pagination@2021-08-27.yang +++ b/yang/clixon/clixon-netconf-list-pagination@2021-08-27.yang @@ -81,6 +81,20 @@ module clixon-netconf-list-pagination { elements."; } grouping pageing-parameters { + leaf list-pagination { + type boolean; + default false; + description + "NETCONF get / get-config needs some way to know that this is a pagination + request, in which case the target is a list/leaf-list and the elements below + (limit/offset/...) are valid. + RESTCONF list pagination has a specific media-type for this purpose. + This is an experimental proposal to make this property explicit. + Possibly there is a better way (annotation?) to signal that this is in fact a + list pagination request. + It is also possible to determine this using heurestics (ie a 'limit' property exixts), + but it seems not 100% deterministic."; + } leaf limit { type union { type uint32;