From f071e0b332b67c956b1276071502604668bcc305 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 8 Dec 2018 18:13:27 +0100 Subject: [PATCH 01/17] prioritized roadmap --- ROADMAP.md | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index e0dd98f3..12ae406f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,26 +1,43 @@ # Clixon roadmap -Not in prio order (yet) +In prio order -- XML - - [Namespace handling](https://github.com/clicon/clixon/issues/49) +High prio +- NACM (RFC 8341) + - Module rules + - Data node rules (read/create/delete/update/execute) +- Special handling of the initial startup transaction to avoid exit at startup + - Possibly - draft-wu-netconf-restconf-factory-restore-03 +- Handle revisions to data model. + - Possibly draft-wang-netmod-module-revision-management-01 + +Medium prio: +- Input validation on custom RPCs/ + - [Sanity checks](https://github.com/clicon/clixon/issues/47) +- Support for XML regex's. + - Currently Posix extended regular expressions +- Support a plugin callback that is invoked when copy-config is called. +- Preserve CLI command history across sessions. The up/down arrows + +Low prio: +- Provide a client library to access netconf APIs provided by system services. +- Support for restconf call-home (RFC 8071) +- Support for restconf PATCH method + +Not prioritized: +- XML [Namespace handling](https://github.com/clicon/clixon/issues/49) - NETCONF - Support for additional Netconf [edit-config modes](https://github.com/clicon/clixon/issues/53) - Netconf [framing](https://github.com/clicon/clixon/issues/50) - - [Sanity checks](https://github.com/clicon/clixon/issues/47) - [Child ordering](https://github.com/clicon/clixon/issues/22) - Netconf backend (Clixon acts as netconf controller) - Restconf - Query parameters -- NACM (RFC 8341) is somewhat limited - - Extend with data node access (read/create/delete/update/execute) - Streams (netconf and restconf) - Extend native stream mode with external persistent timeseries database, eg influxdb. - Jenkins CI/CD and webhooks - YANG - - [Cardinality](https://github.com/clicon/clixon/issues/48) - RFC 6022 [NETCONF monitoring](https://github.com/clicon/clixon/issues/39) - - Factory default Setting - draft-wu-netconf-restconf-factory-restore-03 - Deviation, belongs-to, min/max-elements, action, unique - Containers - [Docker improvements](https://github.com/clicon/clixon/issues/44) From f872c7e295422881168465e3e6c062314a27dff0 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Fri, 21 Dec 2018 01:33:41 +0100 Subject: [PATCH 02/17] * More precise Yang validation and better error messages * For Example, adding bad-, missing-, or unknown-element error messages, etc instead of operation-failed * Removed delete-config support for candidate db since it is not supported in RFC6241. * Switched the order of `error-type` and `error-tag` in all netconf and restconf error messages to comply to RFC order. * Added example_rpc RPC to example backend * Renamed xml_namespace[_set]() to xml_prefix[_set]() * Some restconf error messages contained "rpc-reply" or "rpc-error" which have now been removed. * Netconf/Restconf RPC extra input arguments are ignored (https://github.com/clicon/clixon/issues/47) --- CHANGELOG.md | 15 +- README.md | 3 +- ROADMAP.md | 20 ++- apps/backend/backend_client.c | 31 ++-- apps/backend/backend_commit.c | 106 +++++++++----- apps/backend/backend_commit.h | 2 +- apps/backend/backend_main.c | 25 +++- apps/netconf/netconf_main.c | 31 ++-- apps/netconf/netconf_rpc.c | 37 +++-- apps/restconf/restconf_lib.c | 9 +- apps/restconf/restconf_methods.c | 161 +++++++++++++-------- example/example_backend.c | 49 ++++++- lib/clixon/clixon_netconf_lib.h | 6 +- lib/clixon/clixon_xml.h | 4 +- lib/clixon/clixon_xml_map.h | 7 +- lib/src/clixon_json.c | 2 +- lib/src/clixon_netconf_lib.c | 81 +++++------ lib/src/clixon_options.c | 12 +- lib/src/clixon_proto_client.c | 2 +- lib/src/clixon_xml.c | 26 ++-- lib/src/clixon_xml_map.c | 235 +++++++++++++++++++++++-------- lib/src/clixon_xml_parse.y | 16 +-- lib/src/clixon_xml_sort.c | 3 + lib/src/clixon_yang.c | 2 +- test/test_cli.sh | 6 +- test/test_feature.sh | 5 +- test/test_identity.sh | 4 +- test/test_leafref.sh | 8 +- test/test_list.sh | 2 +- test/test_nacm.sh | 12 +- test/test_nacm_ext.sh | 12 +- test/test_nacm_protocol.sh | 14 +- test/test_netconf.sh | 4 +- test/test_order.sh | 4 +- test/test_perf.sh | 2 +- test/test_restconf.sh | 57 ++++---- test/test_restconf2.sh | 10 +- test/test_rpc.sh | 125 ++++++++++++++++ test/test_stream.sh | 8 +- test/test_type.sh | 6 +- test/test_union.sh | 2 +- test/test_when_must.sh | 8 +- test/test_yang.sh | 8 +- test/test_yang_load.sh | 28 ++-- test/test_yang_namespace.sh | 2 +- 45 files changed, 807 insertions(+), 405 deletions(-) create mode 100755 test/test_rpc.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 160f6254..0035a93d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,6 @@ * [Roadmap](ROADMAP.md) (Uncommitted and unprioritized) ### Major New features - * NACM extension (RFC8341) * NACM module support (RFC8341 A1+A2) * Recovery user "_nacm_recovery" added. @@ -20,6 +19,8 @@ * Support of submodule, include and belongs-to. * Openconfig yang specs parsed: https://github.com/openconfig/public * Improved "unknown" handling + * More precise Yang validation and better error messages + * For Example, adding bad-, missing-, or unknown-element error messages, etc instead of operation-failed * Yang load file configure options changed * `CLICON_YANG_DIR` is changed from a single directory to a path of directories * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list @@ -27,18 +28,20 @@ * CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded. * Correct XML namespace handling * XML multiple modules was based on "loose" semantics so that yang modules were found by iterating thorugh namespaces until a match was made. This did not adhere to proper [XML namespace handling](https://www.w3.org/TR/2009/REC-xml-names-20091208), and causes problems with overlapping names and false positives. Below see XML accepted (but wrong), and correct namespace declaration: -``` + ``` # Wrong but accepted # Correct -``` + ``` * To keep old loose semantics set config option CLICON_XML_NS_ITERATE (true by default) * XML to JSON translator support for mapping xmlns attribute to module name prefix. * Default namespace is still "urn:ietf:params:xml:ns:netconf:base:1.0" * See https://github.com/clicon/clixon/issues/49 ### API changes on existing features (you may need to change your code) +* Removed delete-config support for candidate db since it is not supported in RFC6241. +* Switched the order of `error-type` and `error-tag` in all netconf and restconf error messages to comply to RFC order. * Yang parser is stricter (see above) which may break parsing of existing yang specs. * XML namespace handling is corrected (see above) * For backward compatibility set config option CLICON_XML_NS_ITERATE @@ -49,6 +52,8 @@ * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h ### Minor changes +* Added example_rpc RPC to example backend +* Renamed xml_namespace[_set]() to xml_prefix[_set]() * Changed all make tags --> make TAGS * Keyvalue datastore removed (it has been disabled since 3.3.3) * Removed return value ymodp from yang parse functions (eg yang_parse()). @@ -60,8 +65,10 @@ * config", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "config", NULL) < 0) goto done; goto ok; } else{ - /* yang spec may be set to anyxml by ingress yang check,...*/ + /* yang spec may be set to anyxmly by ingress yang check,...*/ if (xml_spec(xc) != NULL) xml_spec_set(xc, NULL); /* Populate XML with Yang spec (why not do this in parser?) @@ -530,7 +530,7 @@ from_client_lock(clicon_handle h, cbuf *cbx = NULL; /* Assist cbuf */ if ((db = netconf_db_find(xe, "target")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) goto done; goto ok; } @@ -589,7 +589,7 @@ from_client_unlock(clicon_handle h, cbuf *cbx = NULL; /* Assist cbuf */ if ((db = netconf_db_find(xe, "target")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) goto done; goto ok; } @@ -651,7 +651,7 @@ from_client_kill_session(clicon_handle h, if ((x = xml_find(xe, "session-id")) == NULL || (str = xml_find_value(x, "body")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "session-id", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "session-id", NULL) < 0) goto done; goto ok; } @@ -709,7 +709,7 @@ from_client_copy_config(clicon_handle h, cbuf *cbx = NULL; /* Assist cbuf */ if ((source = netconf_db_find(xe, "source")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) goto done; goto ok; } @@ -724,7 +724,7 @@ from_client_copy_config(clicon_handle h, goto ok; } if ((target = netconf_db_find(xe, "target")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) goto done; goto ok; } @@ -777,7 +777,7 @@ from_client_delete_config(clicon_handle h, if ((target = netconf_db_find(xe, "target")) == NULL|| strcmp(target, "running")==0){ - if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) goto done; goto ok; } @@ -856,7 +856,7 @@ from_client_create_subscription(clicon_handle h, if ((x = xpath_first(xe, "//stopTime")) != NULL){ if ((stoptime = xml_find_value(x, "body")) != NULL && str2time(stoptime, &stop) < 0){ - if (netconf_bad_element(cbret, "application", "stopTime", "Expected timestamp") < 0) + if (netconf_bad_element(cbret, "application", "stopTime", "Expected timestamp") < 0) goto done; goto ok; } @@ -864,7 +864,7 @@ from_client_create_subscription(clicon_handle h, if ((x = xpath_first(xe, "//startTime")) != NULL){ if ((starttime = xml_find_value(x, "body")) != NULL && str2time(starttime, &start) < 0){ - if (netconf_bad_element(cbret, "application", "startTime", "Expected timestamp") < 0) + if (netconf_bad_element(cbret, "application", "startTime", "Expected timestamp") < 0) goto done; goto ok; } @@ -925,7 +925,7 @@ from_client_debug(clicon_handle h, char *valstr; if ((valstr = xml_find_body(xe, "level")) == NULL){ - if (netconf_missing_element(cbret, "application", "level", NULL) < 0) + if (netconf_missing_element(cbret, "application", "level", NULL) < 0) goto done; goto ok; } @@ -993,13 +993,10 @@ from_client_msg(clicon_handle h, /* Populate incoming XML tree with yang */ if (xml_spec_populate_rpc(h, x, yspec) < 0) goto done; - if ((ret = xml_yang_validate_rpc(x)) < 0) + if ((ret = xml_yang_validate_rpc(x, cbret)) < 0) goto done; - if (ret == 0){ - if (netconf_operation_failed(cbret, "application", "Validation failed")< 0) - goto done; + if (ret == 0) goto reply; - } xe = NULL; username = xml_find_value(x, "username"); while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) { @@ -1059,7 +1056,7 @@ from_client_msg(clicon_handle h, } else if (strcmp(rpc, "validate") == 0){ if ((db = netconf_db_find(xe, "source")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) goto done; goto reply; } diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 3aa2f82d..88ea9e99 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -81,68 +81,86 @@ * are if code comes via XML/NETCONF. * @param[in] yspec Yang spec * @param[in] td Transaction data + * @param[out] cbret Cligen buffer containing netconf error (if retval == 0) + * @retval -1 Error + * @retval 0 Validation failed (with cbret set) + * @retval 1 Validation OK */ static int generic_validate(yang_spec *yspec, - transaction_data_t *td) + transaction_data_t *td, + cbuf *cbret) { int retval = -1; cxobj *x1; cxobj *x2; yang_stmt *ys; int i; + int ret; /* All entries */ - if (xml_apply(td->td_target, CX_ELMNT, - (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) + if ((ret = xml_yang_validate_all_top(td->td_target, cbret)) < 0) goto done; - + if (ret == 0) + goto fail; /* changed entries */ for (i=0; itd_clen; i++){ x1 = td->td_scvec[i]; /* source changed */ x2 = td->td_tcvec[i]; /* target changed */ - if (xml_yang_validate_add(x2, NULL) < 0) + /* Should this be recursive? */ + if ((ret = xml_yang_validate_add(x2, cbret)) < 0) goto done; + if (ret == 0) + goto fail; } /* deleted entries */ for (i=0; itd_dlen; i++){ x1 = td->td_dvec[i]; ys = xml_spec(x1); if (ys && yang_mandatory(ys)){ - clicon_err(OE_CFG, 0,"Removed mandatory variable: %s", - xml_name(x1)); - goto done; + if (netconf_missing_element(cbret, "protocol", xml_name(x1), "Removed mandatory variable") < 0) + goto done; + goto fail; } } /* added entries */ for (i=0; itd_alen; i++){ x2 = td->td_avec[i]; - if (xml_apply0(x2, CX_ELMNT, - (xml_applyfn_t*)xml_yang_validate_add, NULL) < 0) + if ((ret = xml_yang_validate_add(x2, cbret)) < 0) goto done; + if (ret == 0) + goto fail; } - retval = 0; + // ok: + retval = 1; done: return retval; + fail: + retval = 0; + goto done; } /*! Common code of candidate_validate and candidate_commit * @param[in] h Clicon handle * @param[in] candidate The candidate database. The wanted backend state - * @retval 0 OK - * @retval -1 Fatal error or validation fail - * @note Need to differentiate between error and validation fail + * @retval -1 Error - or validation failed (but cbret not set) + * @retval 0 Validation failed (with cbret set) + * @retval 1 Validation OK + * @note Need to differentiate between error and validation fail + * (only done for generic_validate) */ static int validate_common(clicon_handle h, char *candidate, - transaction_data_t *td) + transaction_data_t *td, + cbuf *cbret) { int retval = -1; yang_spec *yspec; int i; cxobj *xn; - + int ret; + if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; @@ -193,9 +211,12 @@ validate_common(clicon_handle h, if (plugin_transaction_begin(h, td) < 0) goto done; - /* 5. Make generic validation on all new or changed data. */ - if (generic_validate(yspec, td) < 0) + /* 5. Make generic validation on all new or changed data. + Note this is only call that uses 3-values */ + if ((ret = generic_validate(yspec, td, cbret)) < 0) goto done; + if (ret == 0) + goto fail; /* 6. Call plugin transaction validate callbacks */ if (plugin_transaction_validate(h, td) < 0) @@ -204,9 +225,12 @@ validate_common(clicon_handle h, /* 7. Call plugin transaction complete callbacks */ if (plugin_transaction_complete(h, td) < 0) goto done; - retval = 0; + retval = 1; done: return retval; + fail: + retval = 0; + goto done; } /*! Do a diff between candidate and running, then start a commit transaction @@ -216,24 +240,30 @@ validate_common(clicon_handle h, * do something more drastic? * @param[in] h Clicon handle * @param[in] candidate A candidate database, not necessarily "candidate" - * @retval 0 OK - * @retval -1 Fatal error or validation fail + * @retval -1 Error - or validation failed (but cbret not set) + * @retval 0 Validation failed (with cbret set) + * @retval 1 Validation OK * @note Need to differentiate between error and validation fail + * (only done for validate_common) */ int candidate_commit(clicon_handle h, - char *candidate) + char *candidate, + cbuf *cbret) { - int retval = -1; + int retval = -1; transaction_data_t *td = NULL; + int ret; /* 1. Start transaction */ if ((td = transaction_new()) == NULL) goto done; - /* Common steps (with validate) */ - if (validate_common(h, candidate, td) < 0) + /* Common steps (with validate). Note this is only call that uses 3-values */ + if ((ret = validate_common(h, candidate, td, cbret)) < 0) goto done; + if (ret == 0) + goto fail; /* 7. Call plugin transaction commit callbacks */ if (plugin_transaction_commit(h, td) < 0) @@ -252,21 +282,23 @@ candidate_commit(clicon_handle h, /* 9. Call plugin transaction end callbacks */ plugin_transaction_end(h, td); - /* 8. Copy running back to candidate in case end functions updated running */ if (xmldb_copy(h, "running", candidate) < 0){ /* ignore errors or signal major setback ? */ clicon_log(LOG_NOTICE, "Error in rollback, trying to continue"); goto done; } - retval = 0; + retval = 1; done: - /* In case of failure, call plugin transaction termination callbacks */ - if (retval < 0 && td) + /* In case of failure (or error), call plugin transaction termination callbacks */ + if (retval < 1 && td) plugin_transaction_abort(h, td); if (td) transaction_free(td); return retval; + fail: + retval = 0; + goto done; } /*! Commit changes from candidate to running @@ -283,6 +315,7 @@ from_client_commit(clicon_handle h, int retval = -1; int piddb; cbuf *cbx = NULL; /* Assist cbuf */ + int ret; /* Check if target locked by other client */ piddb = xmldb_islocked(h, "running"); @@ -296,9 +329,10 @@ from_client_commit(clicon_handle h, goto done; goto ok; } - if (candidate_commit(h, "candidate") < 0){ /* Assume validation fail, nofatal */ + if ((ret = candidate_commit(h, "candidate", cbret)) < 0){ /* Assume validation fail, nofatal */ clicon_debug(1, "Commit candidate failed"); - if (netconf_invalid_value(cbret, "protocol", clicon_err_reason)< 0) + if (ret < 0) + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) goto done; goto ok; } @@ -367,6 +401,7 @@ from_client_validate(clicon_handle h, { int retval = -1; transaction_data_t *td = NULL; + int ret; if (strcmp(db, "candidate") != 0 && strcmp(db, "tmp") != 0){ if (netconf_invalid_value(cbret, "protocol", "No such database")< 0) @@ -379,11 +414,12 @@ from_client_validate(clicon_handle h, if ((td = transaction_new()) == NULL) goto done; /* Common steps (with commit) */ - if (validate_common(h, db, td) < 0){ + if ((ret = validate_common(h, db, td, cbret)) < 1){ clicon_debug(1, "Validate %s failed", db); - /* XXX: candidate_validate should have proper error handling */ - if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) - goto done; + if (ret < 0){ + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; + } goto ok; } /* Optionally write (potentially modified) tree back to candidate */ diff --git a/apps/backend/backend_commit.h b/apps/backend/backend_commit.h index 6be05858..45b0f922 100644 --- a/apps/backend/backend_commit.h +++ b/apps/backend/backend_commit.h @@ -43,6 +43,6 @@ int from_client_validate(clicon_handle h, char *db, cbuf *cbret); int from_client_commit(clicon_handle h, int pid, cbuf *cbret); int from_client_discard_changes(clicon_handle h, int pid, cbuf *cbret); -int candidate_commit(clicon_handle h, char *db); +int candidate_commit(clicon_handle h, char *db, cbuf *cbret); #endif /* _BACKEND_COMMIT_H_ */ diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index ec4a99d7..8e433d85 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -386,6 +386,7 @@ startup_mode_running(clicon_handle h, char *extraxml_file) { int retval = -1; + cbuf *cbret = NULL; /* Stash original running to candidate for later commit */ if (xmldb_copy(h, "running", "candidate") < 0) @@ -405,15 +406,20 @@ startup_mode_running(clicon_handle h, /* Clear running db */ if (db_reset(h, "running") < 0) goto done; + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } /* Commit original running. Assume -1 is validate fail */ - if (candidate_commit(h, "candidate") < 0){ + if (candidate_commit(h, "candidate", cbret) < 1){ /* (1) We cannot differentiate between fatal errors and validation * failures * (2) If fatal error, we should exit * (3) If validation fails we cannot continue. How could we? * (4) Need to restore the running db since we destroyed it above */ - clicon_log(LOG_NOTICE, "%s: Commit of saved running failed, exiting.", __FUNCTION__); + clicon_log(LOG_NOTICE, "%s: Commit of saved running failed, exiting: %s.", + __FUNCTION__, cbuf_get(cbret)); /* Reinstate original */ if (xmldb_copy(h, "candidate", "running") < 0) goto done; @@ -424,6 +430,8 @@ startup_mode_running(clicon_handle h, goto done; retval = 0; done: + if (cbret) + cbuf_free(cbret); if (xmldb_delete(h, "tmp") < 0) goto done; return retval; @@ -455,6 +463,7 @@ startup_mode_startup(clicon_handle h, char *extraxml_file) { int retval = -1; + cbuf *cbret = NULL; /* Stash original running to backup */ if (xmldb_copy(h, "running", "backup") < 0) @@ -478,13 +487,19 @@ startup_mode_startup(clicon_handle h, /* Clear running db */ if (db_reset(h, "running") < 0) goto done; + /* Create return buffer (not used) */ + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } /* Commit startup */ - if (candidate_commit(h, "startup") < 0){ /* diff */ + if (candidate_commit(h, "startup", cbret) < 1){ /* diff */ /* We cannot differentiate between fatal errors and validation * failures * In both cases we copy back the original running and quit */ - clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting.", __FUNCTION__); + clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting: %s.", + __FUNCTION__, cbuf_get(cbret)); if (xmldb_copy(h, "backup", "running") < 0) goto done; goto done; @@ -494,6 +509,8 @@ startup_mode_startup(clicon_handle h, goto done; retval = 0; done: + if (cbret) + cbuf_free(cbret); if (xmldb_delete(h, "backup") < 0) goto done; if (xmldb_delete(h, "tmp") < 0) diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index 55ee628b..cc4c9710 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -83,15 +83,17 @@ static int process_incoming_packet(clicon_handle h, cbuf *cb) { - char *str; - char *str0; - cxobj *xreq = NULL; /* Request (in) */ - int isrpc = 0; /* either hello or rpc */ - cbuf *cbret = NULL; - cxobj *xret = NULL; /* Return (out) */ - cxobj *xrpc; - cxobj *xc; + int retval = -1; + char *str; + char *str0; + cxobj *xreq = NULL; /* Request (in) */ + int isrpc = 0; /* either hello or rpc */ + cbuf *cbret = NULL; + cxobj *xret = NULL; /* Return (out) */ + cxobj *xrpc; + cxobj *xc; yang_spec *yspec; + int ret; clicon_debug(1, "RECV"); clicon_debug(2, "%s: RCV: \"%s\"", __FUNCTION__, cbuf_get(cb)); @@ -115,15 +117,12 @@ process_incoming_packet(clicon_handle h, } free(str0); if ((xrpc=xpath_first(xreq, "//rpc")) != NULL){ - int ret; isrpc++; - if ((ret = xml_yang_validate_rpc(xrpc)) < 0) + if ((ret = xml_yang_validate_rpc(xrpc, cbret)) < 0) goto done; if (ret == 0){ - if (netconf_operation_failed(cbret, "application", "Validation failed")< 0) - goto done; netconf_output(1, cbret, "rpc-error"); - goto done; + goto ok; } } else @@ -168,6 +167,8 @@ process_incoming_packet(clicon_handle h, } } } + ok: + retval = 0; done: if (xreq) xml_free(xreq); @@ -175,7 +176,7 @@ process_incoming_packet(clicon_handle h, xml_free(xret); if (cbret) cbuf_free(cbret); - return 0; + return retval; } /*! Get netconf message: detect end-of-msg @@ -229,7 +230,7 @@ netconf_input_cb(int s, /* Remove trailer */ *(((char*)cbuf_get(cb)) + cbuf_len(cb) - strlen("]]>]]>")) = '\0'; if (process_incoming_packet(h, cb) < 0) - goto done; + ; //goto done; // ignore errors if (cc_closed) break; cbuf_reset(cb); diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index f53b8675..64001137 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -56,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -876,12 +877,16 @@ netconf_application_rpc(clicon_handle h, cbuf *cb = NULL; cbuf *cbret = NULL; int ret; - + /* First check system / netconf RPC:s */ if ((cb = cbuf_new()) == NULL){ clicon_err(OE_UNIX, 0, "cbuf_new"); goto done; } + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "cbuf_new"); + goto done; + } /* Find yang rpc statement, return yang rpc statement if found Check application RPC */ if ((yspec = clicon_dbspec_yang(h)) == NULL){ @@ -917,15 +922,18 @@ netconf_application_rpc(clicon_handle h, xml_spec_set(xn, yinput); /* needed for xml_spec_populate */ if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; - if (xml_apply(xn, CX_ELMNT, - (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) + if ((ret = xml_yang_validate_all_top(xn, cbret)) < 0) goto done; - if (xml_yang_validate_add(xn, NULL) < 0) + if (ret == 0){ + netconf_output(1, cbret, "rpc-error"); + goto ok; + } + if ((ret = xml_yang_validate_add(xn, cbret)) < 0) goto done; - } - if ((cbret = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, 0, "cbuf_new"); - goto done; + if (ret == 0){ + netconf_output(1, cbret, "rpc-error"); + goto ok; + } } /* Look for local (client-side) netconf plugins. */ if ((ret = rpc_callback_call(h, xn, cbret, NULL)) < 0) @@ -943,11 +951,18 @@ netconf_application_rpc(clicon_handle h, xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; - if (xml_apply(xoutput, CX_ELMNT, - (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) + if ((ret = xml_yang_validate_all_top(xoutput, cbret)) < 0) goto done; - if (xml_yang_validate_add(xoutput, NULL) < 0) + if (ret == 0){ + clicon_log(LOG_WARNING, "Errors in output netconf %s", cbuf_get(cbret)); + goto ok; + } + if ((ret = xml_yang_validate_add(xoutput, cbret)) < 0) goto done; + if (ret == 0){ + clicon_log(LOG_WARNING, "Errors in output netconf %s", cbuf_get(cbret)); + goto ok; + } } retval = 1; /* handled by callback */ goto done; diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 75c06699..be214fbb 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -71,11 +71,12 @@ static const map_str2int netconf_restconf_map[] = { {"missing-attribute", 400}, {"bad-attribute", 400}, {"unknown-attribute", 400}, + {"missing-element", 400}, {"bad-element", 400}, {"unknown-element", 400}, {"unknown-namespace", 400}, - {"access-denied", 401}, - {"access-denied", 403}, + {"access-denied", 401}, /* or 403 */ + {"access-denied", 403}, {"lock-denied", 409}, {"resource-denied", 409}, {"rollback-failed", 500}, @@ -436,7 +437,8 @@ api_return_err(clicon_handle h, goto ok; } tagstr = xml_body(xtag); - code = restconf_err2code(tagstr); + if ((code = restconf_err2code(tagstr)) < 0) + code = 500; /* internal server error */ if ((reason_phrase = restconf_code2reason(code)) == NULL) reason_phrase=""; if (xml_name_set(xerr, "error") < 0) @@ -448,6 +450,7 @@ api_return_err(clicon_handle h, else if (xml2json_cbuf(cb, xerr, pretty) < 0) goto done; + FCGX_SetExitStatus(code, r->out); /* Created */ FCGX_FPrintF(r->out, "Status: %d %s\r\n", code, reason_phrase); FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n\r\n", use_xml?"xml":"json"); diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 42d4d331..b8beb068 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -190,7 +190,7 @@ api_data_get2(clicon_handle h, yang_spec *yspec; cxobj *xret = NULL; cxobj *xerr = NULL; /* malloced */ - cxobj *xe; + cxobj *xe = NULL; cxobj **xvec = NULL; size_t xlen; int i; @@ -206,8 +206,9 @@ api_data_get2(clicon_handle h, if (api_path2xpath_cvv(yspec, pcvec, pi, cbpath) < 0){ if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } path = cbuf_get(cbpath); @@ -215,8 +216,9 @@ api_data_get2(clicon_handle h, if (clicon_rpc_get(h, path, &xret) < 0){ if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } /* We get return via netconf which is complete tree from root @@ -434,16 +436,18 @@ api_data_post(clicon_handle h, if (xml_parse_string(data, NULL, &xdata) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } } else if (json_parse_str(data, &xdata) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } /* 4.4.1: The message-body MUST contain exactly one instance of the @@ -452,8 +456,9 @@ api_data_post(clicon_handle h, if (xml_child_nr(xdata) != 1){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } x = xml_child_i(xdata,0); @@ -662,16 +667,18 @@ api_data_put(clicon_handle h, if (xml_parse_string(data, NULL, &xdata) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } } else if (json_parse_str(data, &xdata) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } /* The message-body MUST contain exactly one instance of the @@ -680,8 +687,9 @@ api_data_put(clicon_handle h, if (xml_child_nr(xdata) != 1){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } x = xml_child_i(xdata,0); @@ -705,8 +713,9 @@ api_data_put(clicon_handle h, if (strcmp(xml_name(x), xml_name(xbot))){ if (netconf_operation_failed_xml(&xerr, "protocol", "Not same symbol in api-path as data") < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } /* If list or leaf-list, api-path keys must match data keys */ @@ -714,8 +723,9 @@ api_data_put(clicon_handle h, if (match_list_keys((yang_stmt*)y, x, xbot) < 0){ if (netconf_operation_failed_xml(&xerr, "protocol", "api-path keys do not match data keys") < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } } @@ -866,7 +876,7 @@ api_data_delete(clicon_handle h, if ((xa = xml_new("operation", xbot, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); - if (xml_value_set(xa, xml_operation2str(op)) < 0) + if (xml_value_set(xa, xml_operation2str(op)) < 0) goto done; if ((cbx = cbuf_new()) == NULL) goto done; @@ -1054,7 +1064,6 @@ api_operations_post(clicon_handle h, cxobj *xdata = NULL; cxobj *xret = NULL; cxobj *xerr = NULL; /* malloced must be freed */ - cxobj *xer; /* non-malloced error */ cbuf *cbx = NULL; cxobj *xtop = NULL; /* xpath root */ cxobj *xbot = NULL; @@ -1076,13 +1085,18 @@ api_operations_post(clicon_handle h, clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; } + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "cbuf_new"); + goto done; + } for (i=0; i h ?*/ if (xml_apply(xbot, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; - if (xml_apply(xbot, CX_ELMNT, - (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) + if ((ret = xml_yang_validate_all(xbot, cbret)) < 0) goto done; - if (xml_yang_validate_add(xbot, NULL) < 0){ - if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) - goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + if (ret == 0){ /* validation failed */ + clicon_debug(1, "%s err: %s", __FUNCTION__, cbuf_get(cbret)); + if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) goto done; + if ((xe=xpath_first(xerr, "rpc-reply/rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } + if ((ret = xml_yang_validate_add(xbot, cbret)) < 0){ + if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) + goto done; + if ((xe=xpath_first(xerr, "rpc-reply/rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + if (xml_apply0(xbot, CX_ELMNT, xml_default, NULL) < 0) + goto done; } } } - if ((cbret = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, 0, "cbuf_new"); - goto done; - } + ret = 0; xe = NULL; while ((xe = xml_child_each(xtop, xe, CX_ELMNT)) != NULL) { /* Look for local (client-side) restconf plugins. */ @@ -1208,8 +1238,8 @@ api_operations_post(clicon_handle h, if (xml_parse_string(cbuf_get(cbret), NULL, &xret) < 0) goto done; /* Local error: return it and quit */ - if ((xer = xpath_first(xret, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xer, pretty, use_xml) < 0) + if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){ + if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; } @@ -1219,8 +1249,8 @@ api_operations_post(clicon_handle h, if (ret == 0){ /* Send to backend */ if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) goto done; - if ((xer = xpath_first(xret, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xer, pretty, use_xml) < 0) + if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){ + if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; } @@ -1240,18 +1270,31 @@ api_operations_post(clicon_handle h, goto done; if ((xoutput=xpath_first(xret, "/")) != NULL){ xml_name_set(xoutput, "output"); -#if 0 - clicon_debug(1, "%s xoutput:%s", __FUNCTION__, cbuf_get(cbx)); -#endif cbuf_reset(cbx); xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; - if (xml_apply(xoutput, CX_ELMNT, - (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) + if ((ret = xml_yang_validate_all(xoutput, cbret)) < 0) goto done; - if (xml_yang_validate_add(xoutput, NULL) < 0) + if (ret == 0){ /* validation failed */ + clicon_debug(1, "%s output validation failed", __FUNCTION__); + if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) + goto done; + if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + if ((ret = xml_yang_validate_add(xoutput, cbret)) < 0) goto done; + if (ret == 0){ /* validation failed */ + if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) + goto done; + if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } } /* Sanity check of outgoing XML */ FCGX_SetExitStatus(200, r->out); /* OK */ diff --git a/example/example_backend.c b/example/example_backend.c index 3a420a0e..35fe20d4 100644 --- a/example/example_backend.c +++ b/example/example_backend.c @@ -132,6 +132,7 @@ fib_route(clicon_handle h, /* Clicon handle */ cprintf(cbret, "" "ipv4" "2.3.4.5" + "static" ""); return 0; } @@ -157,16 +158,41 @@ route_count(clicon_handle h, * in [RFC6241]. */ static int -empty(clicon_handle h, /* Clicon handle */ - cxobj *xe, /* Request: */ - cbuf *cbret, /* Reply eg ... */ - void *arg, /* client_entry */ - void *regarg) /* Argument given at register */ +empty_rpc(clicon_handle h, /* Clicon handle */ + cxobj *xe, /* Request: */ + cbuf *cbret, /* Reply eg ... */ + void *arg, /* client_entry */ + void *regarg) /* Argument given at register */ { cprintf(cbret, ""); return 0; } +/*! More elaborate example RPC for testing + * The RPC returns the incoming parameters + */ +static int +example_rpc(clicon_handle h, /* Clicon handle */ + cxobj *xe, /* Request: */ + cbuf *cbret, /* Reply eg ... */ + void *arg, /* client_entry */ + void *regarg) /* Argument given at register */ +{ + int retval = -1; + cxobj *x = NULL; + + cprintf(cbret, ""); + while ((x = xml_child_each(xe, x, CX_ELMNT)) != NULL) { + if (clicon_xml2cbuf(cbret, x, 0, 0) < 0) + goto done; + } + cprintf(cbret, ""); + retval = 0; + done: + return retval; +} + + /*! Called to get state data from plugin * @param[in] h Clicon handle * @param[in] xpath String with XPATH syntax. or NULL for all @@ -315,22 +341,31 @@ clixon_plugin_init(clicon_handle h) if (example_stream_timer_setup(h) < 0) goto done; - /* Register callback for routing rpc calls */ + /* Register callback for routing rpc calls + */ + if (rpc_callback_register(h, fib_route, NULL, "fib-route"/* Xml tag when callback is made */ ) < 0) goto done; + /* From ietf-routing.yang */ if (rpc_callback_register(h, route_count, NULL, "route-count"/* Xml tag when callback is made */ ) < 0) goto done; - if (rpc_callback_register(h, empty, + /* From example.yang (clicon) */ + if (rpc_callback_register(h, empty_rpc, NULL, "empty"/* Xml tag when callback is made */ ) < 0) goto done; + if (rpc_callback_register(h, example_rpc, + NULL, + "example"/* Xml tag when callback is made */ + ) < 0) + goto done; /* Return plugin API */ return &api; diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index cdf570e9..b22e697b 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -46,9 +46,9 @@ int netconf_too_big(cbuf *cb, char *type, char *message); int netconf_missing_attribute(cbuf *cb, char *type, char *info, char *message); int netconf_bad_attribute(cbuf *cb, char *type, char *info, char *message); int netconf_unknown_attribute(cbuf *cb, char *type, char *info, char *message); -int netconf_missing_element(cbuf *cb, char *type, char *info, char *message); -int netconf_bad_element(cbuf *cb, char *type, char *info, char *message); -int netconf_unknown_element(cbuf *cb, char *type, char *info, char *message); +int netconf_missing_element(cbuf *cb, char *type, char *element, char *message); +int netconf_bad_element(cbuf *cb, char *type, char *info, char *element); +int netconf_unknown_element(cbuf *cb, char *type, char *element, char *message); int netconf_unknown_namespace(cbuf *cb, char *type, char *info, char *message); int netconf_access_denied(cbuf *cb, char *type, char *message); int netconf_access_denied_xml(cxobj **xret, char *type, char *message); diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index 803e5948..68f332ab 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -99,8 +99,8 @@ extern int _CLICON_XML_NS_ITERATE; char *xml_type2str(enum cxobj_type type); char *xml_name(cxobj *xn); int xml_name_set(cxobj *xn, char *name); -char *xml_namespace(cxobj *xn); -int xml_namespace_set(cxobj *xn, char *name); +char *xml_prefix(cxobj *xn); +int xml_prefix_set(cxobj *xn, char *name); int xml2ns(cxobj *x, char *localname, char **namespace); cxobj *xml_parent(cxobj *xn); int xml_parent_set(cxobj *xn, cxobj *parent); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index abd1fe50..dba82884 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -43,9 +43,10 @@ */ int xml2txt(FILE *f, cxobj *x, int level); int xml2cli(FILE *f, cxobj *x, char *prepend, enum genmodel_type gt); -int xml_yang_validate_rpc(cxobj *xrpc); -int xml_yang_validate_add(cxobj *xt, void *arg); -int xml_yang_validate_all(cxobj *xt, void *arg); +int xml_yang_validate_rpc(cxobj *xrpc, cbuf *cbret); +int xml_yang_validate_add(cxobj *xt, cbuf *cbret); +int xml_yang_validate_all(cxobj *xt, cbuf *cbret); +int xml_yang_validate_all_top(cxobj *xt, cbuf *cbret); int xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0); int cvec2xml_1(cvec *cvv, char *toptag, cxobj *xp, cxobj **xt0); int xml_diff(yang_spec *yspec, cxobj *xt1, cxobj *xt2, diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index 00778c30..06aa71c9 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -315,7 +315,7 @@ xml2json1_cbuf(cbuf *cb, * Harder if x has a prefix, then that should also be translated to associated * module name */ - prefix = xml_namespace(x); + prefix = xml_prefix(x); if (xml2ns(x, prefix, &namespace) < 0) goto done; if ((ys = xml_spec(x)) != NULL) /* yang spec associated with x */ diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index ccb88275..6c284fc6 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -80,8 +80,8 @@ netconf_in_use(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "in-use" "%s" + "in-use" "error", type) <0) goto err; @@ -119,8 +119,8 @@ netconf_invalid_value(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "invalid-value" "%s" + "invalid-value" "error", type) <0) goto err; @@ -159,8 +159,8 @@ netconf_too_big(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "too-big" "%s" + "too-big" "error", type) <0) goto err; @@ -200,8 +200,8 @@ netconf_missing_attribute(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "missing-attribute" "%s" + "missing-attribute" "%s" "error", type, info) <0) @@ -241,8 +241,8 @@ netconf_bad_attribute(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "bad-attribute" "%s" + "bad-attribute" "%s" "error", type, info) <0) @@ -283,8 +283,8 @@ netconf_unknown_attribute(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "unknown-attribute" "%s" + "unknown-attribute" "%s" "error", type, info) <0) @@ -318,18 +318,18 @@ netconf_unknown_attribute(cbuf *cb, int netconf_missing_element(cbuf *cb, char *type, - char *info, + char *element, char *message) { int retval = -1; char *encstr = NULL; if (cprintf(cb, "" - "missing-element" "%s" - "%s" + "missing-element" + "%s" "error", - type, info) <0) + type, element) <0) goto err; if (message){ if (xml_chardata_encode(&encstr, "%s", message) < 0) @@ -355,24 +355,24 @@ netconf_missing_element(cbuf *cb, * pattern mismatch. * @param[out] cb CLIgen buf. Error XML is written in this buffer * @param[in] type Error type: "application" or "protocol" - * @param[in] info bad-element xml + * @param[in] elemnt Bad element name * @param[in] message Error message */ int netconf_bad_element(cbuf *cb, char *type, - char *info, + char *element, char *message) { int retval = -1; char *encstr = NULL; if (cprintf(cb, "" - "bad-element" "%s" - "%s" + "bad-element" + "%s" "error", - type, info) <0) + type, element) <0) goto err; if (message){ if (xml_chardata_encode(&encstr, "%s", message) < 0) @@ -397,24 +397,24 @@ netconf_bad_element(cbuf *cb, * An unexpected element is present. * @param[out] cb CLIgen buf. Error XML is written in this buffer * @param[in] type Error type: "application" or "protocol" - * @param[in] info bad-element xml + * @param[in] element Bad element name * @param[in] message Error message */ int netconf_unknown_element(cbuf *cb, char *type, - char *info, + char *element, char *message) { int retval = -1; char *encstr = NULL; if (cprintf(cb, "" - "unknown-element" "%s" - "%s" + "unknown-element" + "%s" "error", - type, info) <0) + type, element) <0) goto err; if (message){ if (xml_chardata_encode(&encstr, "%s", message) < 0) @@ -452,8 +452,8 @@ netconf_unknown_namespace(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "unknown-namespace" "%s" + "unknown-namespace" "%s" "error", type, info) <0) @@ -478,7 +478,8 @@ netconf_unknown_namespace(cbuf *cb, /*! Create Netconf access-denied error XML tree according to RFC 6241 App A * - * An expected element is missing. + * Access to the requested protocol operation or data model is denied because + * authorization failed. * @param[out] cb CLIgen buf. Error XML is written in this buffer * @param[in] type Error type: "application" or "protocol" * @param[in] message Error message @@ -492,8 +493,8 @@ netconf_access_denied(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "access-denied" "%s" + "access-denied" "error", type) <0) goto err; @@ -517,7 +518,8 @@ netconf_access_denied(cbuf *cb, /*! Create Netconf access-denied error XML tree according to RFC 6241 App A * - * An expected element is missing. + * Access to the requested protocol operation or data model is denied because + * authorization failed. * @param[out] xret Error XML tree * @param[in] type Error type: "application" or "protocol" * @param[in] message Error message @@ -538,8 +540,8 @@ netconf_access_denied_xml(cxobj **xret, goto done; if ((xerr = xml_new("rpc-error", *xret, NULL)) == NULL) goto done; - if (xml_parse_va(&xerr, NULL, "access-denied" - "%s" + if (xml_parse_va(&xerr, NULL, "%s" + "access-denied" "error", type) < 0) goto done; if (message && xml_parse_va(&xerr, NULL, "%s", @@ -567,8 +569,8 @@ netconf_lock_denied(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "lock-denied" "protocol" + "lock-denied" "%s" "error", info) <0) @@ -593,7 +595,7 @@ netconf_lock_denied(cbuf *cb, /*! Create Netconf resource-denied error XML tree according to RFC 6241 App A * - * An expected element is missing. + * Request could not be completed because of insufficient resources. * @param[out] cb CLIgen buf. Error XML is written in this buffer * @param[in] type Error type: "transport, "rpc", "application", "protocol" * @param[in] message Error message @@ -607,8 +609,8 @@ netconf_resource_denied(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "resource-denied" "%s" + "resource-denied" "error", type) <0) goto err; @@ -647,8 +649,8 @@ netconf_rollback_failed(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "rollback-failed" "%s" + "rollback-failed" "error", type) <0) goto err; @@ -686,8 +688,8 @@ netconf_data_exists(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "data-exists" "application" + "data-exists" "error") <0) goto err; if (message){ @@ -724,8 +726,8 @@ netconf_data_missing(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "data-missing" "application" + "data-missing" "error") <0) goto err; if (message){ @@ -763,8 +765,8 @@ netconf_operation_not_supported(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "operation-not-supported" "%s" + "operation-not-supported" "error", type) <0) goto err; @@ -803,8 +805,8 @@ netconf_operation_failed(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "operation-failed" "%s" + "operation-failed" "error", type) <0) goto err; @@ -850,8 +852,8 @@ netconf_operation_failed_xml(cxobj **xret, goto done; if ((xerr = xml_new("rpc-error", *xret, NULL)) == NULL) goto done; - if (xml_parse_va(&xerr, NULL, "operation-failed" - "%s" + if (xml_parse_va(&xerr, NULL, "%s" + "operation-failed" "error", type) < 0) goto done; if (message && xml_parse_va(&xerr, NULL, "%s", @@ -879,8 +881,8 @@ netconf_malformed_message(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "malformed-message" "rpc" + "malformed-message" "error") <0) goto err; if (message){ @@ -925,8 +927,8 @@ netconf_malformed_message_xml(cxobj **xret, goto done; if ((xerr = xml_new("rpc-error", *xret, NULL)) == NULL) goto done; - if (xml_parse_va(&xerr, NULL, "malformed-message" - "rpc" + if (xml_parse_va(&xerr, NULL, "rpc" + "malformed-message" "error") < 0) goto done; if (message && xml_parse_va(&xerr, NULL, "%s", @@ -990,7 +992,6 @@ netconf_module_load(clicon_handle h) clicon_err(OE_CFG, ENOENT, "Clicon configuration not loaded"); goto done; } - /* Enable features (hardcoded here) */ if (xml_parse_string("ietf-netconf:candidate", yspec, &xc) < 0) goto done; diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 873d3aaf..45c3948b 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -136,6 +136,8 @@ parse_configfile(clicon_handle h, char *name; char *body; clicon_hash_t *copt = clicon_options(h); + cbuf *cbret = NULL; + int ret; if (filename == NULL || !strlen(filename)){ clicon_err(OE_UNIX, 0, "Not specified"); @@ -167,8 +169,16 @@ parse_configfile(clicon_handle h, } if (xml_apply0(xc, CX_ELMNT, xml_default, yspec) < 0) goto done; - if (xml_apply0(xc, CX_ELMNT, xml_yang_validate_add, NULL) < 0) + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); goto done; + } + if ((ret = xml_yang_validate_add(xc, cbret)) < 0) + goto done; + if (ret == 0){ + clicon_err(OE_CFG, 0, "Config file validation: %s", cbuf_get(cbret)); + goto done; + } while ((x = xml_child_each(xc, x, CX_ELMNT)) != NULL) { name = xml_name(x); body = xml_body(x); diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 0cf421d2..aa3ac4ac 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -444,7 +444,7 @@ clicon_rpc_delete_config(clicon_handle h, char *username; username = clicon_username_get(h); - if ((msg = clicon_msg_encode("<%s/>", + if ((msg = clicon_msg_encode("<%s/>none", username?username:"", db)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 24bab41b..59292ba9 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -196,10 +196,9 @@ xml_name_set(cxobj *xn, /*! Get namespace of xnode * @param[in] xn xml node * @retval namespace of xml node - * XXX change to xml_localname */ char* -xml_namespace(cxobj *xn) +xml_prefix(cxobj *xn) { return xn->x_prefix; } @@ -209,11 +208,10 @@ xml_namespace(cxobj *xn) * @param[in] localname new namespace, null-terminated string, copied by function * @retval -1 on error with clicon-err set * @retval 0 OK - * XXX change to xml_localname_set */ int -xml_namespace_set(cxobj *xn, - char *localname) +xml_prefix_set(cxobj *xn, + char *localname) { if (xn->x_prefix){ free(xn->x_prefix); @@ -288,7 +286,7 @@ xmlns_check(cxobj *xn, char *xns; while ((x = xml_child_each(xn, x, CX_ATTR)) != NULL) - if ((xns = xml_namespace(x)) && strcmp(xns, "xmlns")==0 && + if ((xns = xml_prefix(x)) && strcmp(xns, "xmlns")==0 && strcmp(xml_name(x), nsn) == 0) return xml_value(x); return NULL; @@ -311,7 +309,7 @@ xml_localname_check(cxobj *xn, yang_stmt *ys = xml_spec(xn); /* No namespace name - comply */ - if ((nsn = xml_namespace(xn)) == NULL) + if ((nsn = xml_prefix(xn)) == NULL) return 0; /* Check if NSN defined in same node */ if (xmlns_check(xn, nsn) != NULL) @@ -965,7 +963,7 @@ xml_find_type_value(cxobj *xt, char *xprefix; /* xprefix */ while ((x = xml_child_each(xt, x, type)) != NULL) { - xprefix = xml_namespace(x); + xprefix = xml_prefix(x); if (prefix) pmatch = xprefix?strcmp(prefix,xprefix)==0:0; else @@ -1121,7 +1119,7 @@ clicon_xml2file(FILE *f, if (x == NULL) goto ok; name = xml_name(x); - namespace = xml_namespace(x); + namespace = xml_prefix(x); switch(xml_type(x)){ case CX_BODY: if ((val = xml_value(x)) == NULL) /* incomplete tree */ @@ -1246,7 +1244,7 @@ clicon_xml2cbuf(cbuf *cb, char *val; name = xml_name(x); - namespace = xml_namespace(x); + namespace = xml_prefix(x); switch(xml_type(x)){ case CX_BODY: if ((val = xml_value(x)) == NULL) /* incomplete tree */ @@ -1333,10 +1331,10 @@ xmltree2cbuf(cbuf *cb, cprintf(cb, " "); if (xml_type(x) != CX_BODY) cprintf(cb, "%s", xml_type2str(xml_type(x))); - if (xml_namespace(x)==NULL) + if (xml_prefix(x)==NULL) cprintf(cb, " %s", xml_name(x)); else - cprintf(cb, " %s:%s", xml_namespace(x), xml_name(x)); + cprintf(cb, " %s:%s", xml_prefix(x), xml_name(x)); if (xml_value(x)) cprintf(cb, " value:\"%s\"", xml_value(x)); if (x->x_flags) @@ -1612,8 +1610,8 @@ xml_copy_one(cxobj *x0, if ((s = xml_name(x0))) /* malloced string */ if ((xml_name_set(x1, s)) < 0) return -1; - if ((s = xml_namespace(x0))) /* malloced string */ - if ((xml_namespace_set(x1, s)) < 0) + if ((s = xml_prefix(x0))) /* malloced string */ + if ((xml_prefix_set(x1, s)) < 0) return -1; return 0; } diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 6794b503..2263bde2 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -87,6 +87,7 @@ #include "clixon_xpath.h" #include "clixon_log.h" #include "clixon_err.h" +#include "clixon_netconf_lib.h" #include "clixon_xml_sort.h" #include "clixon_xml_map.h" @@ -230,10 +231,15 @@ xml2cli(FILE *f, /*! Validate xml node of type leafref, ensure the value is one of that path's reference * @param[in] xt XML leaf node of type leafref * @param[in] ytype Yang type statement belonging to the XML node + * @param[out] cbret Error buffer + * @retval 1 Validation OK + * @retval 0 Validation failed + * @retval -1 Error */ static int validate_leafref(cxobj *xt, - yang_stmt *ytype) + yang_stmt *ytype, + cbuf *cbret) { int retval = -1; yang_stmt *ypath; @@ -247,8 +253,9 @@ validate_leafref(cxobj *xt, if ((leafrefbody = xml_body(xt)) == NULL) goto ok; if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){ - clicon_err(OE_DB, 0, "Leafref %s requires path statement", ytype->ys_argument); - goto done; + if (netconf_missing_element(cbret, "application", ytype->ys_argument, "Leafref requires path statement") < 0) + goto done; + goto fail; } if (xpath_vec(xt, "%s", &xvec, &xlen, ypath->ys_argument) < 0) goto done; @@ -260,9 +267,9 @@ validate_leafref(cxobj *xt, break; } if (i==xlen){ - clicon_err(OE_DB, 0, "Leafref validation failed, no such leaf: %s", - leafrefbody); - goto done; + if (netconf_bad_element(cbret, "application", leafrefbody, "Leafref validation failed: No such leaf") < 0) + goto done; + goto fail; } ok: retval = 0; @@ -270,6 +277,9 @@ validate_leafref(cxobj *xt, if (xvec) free(xvec); return retval; + fail: + retval = 0; + goto done; } /*! Validate xml node of type identityref, ensure value is a defined identity @@ -285,14 +295,18 @@ validate_leafref(cxobj *xt, * @param[in] xt XML leaf node of type identityref * @param[in] ys Yang spec of leaf * @param[in] ytype Yang type field of type identityref + * @param[out] cbret Error buffer + * @retval 1 Validation OK + * @retval 0 Validation failed + * @retval -1 Error * @see ys_populate_identity where the derived types are set * @see RFC7950 Sec 9.10.2: - */ static int validate_identityref(cxobj *xt, yang_stmt *ys, - yang_stmt *ytype) + yang_stmt *ytype, + cbuf *cbret) { int retval = -1; char *node; @@ -305,37 +319,46 @@ validate_identityref(cxobj *xt, * Always add default prefix because derived identifiers are stored with * prefixes in the base identifiers derived-list. */ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } if ((node = xml_body(xt)) == NULL) return 0; if (strchr(node, ':') == NULL){ prefix = yang_find_myprefix(ys); - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } cprintf(cb, "%s:%s", prefix, node); node = cbuf_get(cb); } /* This is the type's base reference */ if ((ybaseref = yang_find((yang_node*)ytype, Y_BASE, NULL)) == NULL){ - clicon_err(OE_DB, 0, "Identityref validation failed, no base"); - goto done; + if (netconf_missing_element(cbret, "application", ytype->ys_argument, "Identityref validation failed, no base") < 0) + goto done; + goto fail; } /* This is the actual base identity */ if ((ybaseid = yang_find_identity(ybaseref, ybaseref->ys_argument)) == NULL){ - clicon_err(OE_DB, 0, "Identityref validation failed, no base identity"); - goto done; + if (netconf_missing_element(cbret, "application", ybaseref->ys_argument, "Identityref validation failed, no base identity") < 0) + goto done; + goto fail; } /* Here check if node is in the derived node list of the base identity */ if (cvec_find(ybaseid->ys_cvec, node) == NULL){ - clicon_err(OE_DB, 0, "Identityref validation failed, %s not derived from %s", node, ybaseid->ys_argument); - goto done; + cbuf_reset(cb); + cprintf(cb, "Identityref validation failed, %s not derived from %s", + node, ybaseid->ys_argument); + if (netconf_operation_failed(cbret, "application", cbuf_get(cb)) < 0) + goto done; + goto fail; } - retval = 0; + retval = 1; done: if (cb) cbuf_free(cb); return retval; + fail: + retval = 0; + goto done; } /*! Validate an RPC node @@ -377,24 +400,33 @@ validate_identityref(cxobj *xt, * the same order as they are defined within the "output" statement. */ int -xml_yang_validate_rpc(cxobj *xrpc) +xml_yang_validate_rpc(cxobj *xrpc, + cbuf *cbret) { int retval = -1; yang_stmt *yn=NULL; /* rpc name */ cxobj *xn; /* rpc name */ - yang_stmt *yi=NULL; /* input name */ - cxobj *xi; /* input name */ + int ret; - assert(strcmp(xml_name(xrpc), "rpc")==0); + if (strcmp(xml_name(xrpc), "rpc")){ + clicon_err(OE_XML, EINVAL, "Expected RPC"); + goto done; + } xn = NULL; + /* xn is name of rpc, ie */ while ((xn = xml_child_each(xrpc, xn, CX_ELMNT)) != NULL) { if ((yn = xml_spec(xn)) == NULL) goto fail; - xi = NULL; - while ((xi = xml_child_each(xn, xi, CX_ELMNT)) != NULL) { - if ((yi = xml_spec(xi)) == NULL) - goto fail; - } + if ((ret = xml_yang_validate_all(xn, cbret)) < 0) + goto fail; + if (ret == 0) + goto fail; + if ((ret = xml_yang_validate_add(xn, cbret)) < 0) + goto fail; + if (ret == 0) + goto fail; + if (xml_apply0(xn, CX_ELMNT, xml_default, NULL) < 0) + goto done; } // ok: /* pass validation */ retval = 1; @@ -408,14 +440,24 @@ xml_yang_validate_rpc(cxobj *xrpc) /*! Validate a single XML node with yang specification for added entry * 1. Check if mandatory leafs present as subs. * 2. Check leaf values, eg int ranges and string regexps. - * @param[in] xt XML node to be validated - * @retval 0 Valid OK - * @retval -1 Validation failed + * @param[in] xt XML node to be validated + * @param[out] cbret Error buffer + * @retval 1 Validation OK + * @retval 0 Validation failed + * @retval -1 Error + * @code + * cxobj *x; + * cbuf *cbret = cbuf_new(); + * if ((ret = xml_yang_validate_add(x, cbret)) < 0) + * err; + * if (ret == 0) + * fail; + * @endcode * @see xml_yang_validate_all */ int xml_yang_validate_add(cxobj *xt, - void *arg) + cbuf *cbret) { int retval = -1; cg_var *cv = NULL; @@ -424,11 +466,14 @@ xml_yang_validate_add(cxobj *xt, int i; yang_stmt *ys; char *body; + int ret; + cxobj *x; /* if not given by argument (overide) use default link and !Node has a config sub-statement and it is false */ if ((ys = xml_spec(xt)) != NULL && yang_config(ys) != 0){ switch (ys->ys_keyword){ + case Y_RPC: case Y_INPUT: case Y_LIST: /* fall thru */ @@ -440,9 +485,9 @@ xml_yang_validate_add(cxobj *xt, if (yang_config(yc)==0) continue; if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){ - clicon_err(OE_CFG, 0,"Missing mandatory variable: %s", - yc->ys_argument); - goto done; + if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory variable") < 0) + goto done; + goto fail; } } break; @@ -463,12 +508,11 @@ xml_yang_validate_add(cxobj *xt, goto done; } if ((ys_cv_validate(cv, ys, &reason)) != 1){ - clicon_err(OE_DB, 0, - "validation of %s failed %s", - ys->ys_argument, reason?reason:""); + if (netconf_bad_element(cbret, "application", ys->ys_argument, reason) < 0) + goto done; if (reason) free(reason); - goto done; + goto fail; } } break; @@ -476,28 +520,43 @@ xml_yang_validate_add(cxobj *xt, break; } } - retval = 0; + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if ((ret = xml_yang_validate_add(x, cbret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + retval = 1; done: if (cv) cv_free(cv); return retval; + fail: + retval = 0; + goto done; } /*! Validate a single XML node with yang specification for all (not only added) entries * 1. Check leafrefs. Eg you delete a leaf and a leafref references it. * @param[in] xt XML node to be validated - * @param[in] arg Not used - * @retval -1 Validation failed - * @retval 0 Validation OK + * @param[out] cbret Error buffer + * @retval 1 Validation OK + * @retval 0 Validation failed + * @retval -1 Error * @see xml_yang_validate_add * @code - * if (xml_apply(x, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate_all, 0) < 0) + * cxobj *x; + * cbuf *cbret = cbuf_new(); + * if ((ret = xml_yang_validate_all(x, cbret)) < 0) * err; + * if (ret == 0) + * fail; * @endcode */ int xml_yang_validate_all(cxobj *xt, - void *arg) + cbuf *cbret) { int retval = -1; yang_stmt *ys; /* yang node */ @@ -505,13 +564,24 @@ xml_yang_validate_all(cxobj *xt, yang_stmt *ye; /* yang must error-message */ char *xpath; int nr; - + int ret; + cxobj *x; + /* if not given by argument (overide) use default link and !Node has a config sub-statement and it is false */ - if ((ys = xml_spec(xt)) != NULL && - yang_config(ys) != 0){ + ys=xml_spec(xt); + if (ys==NULL){ + if (netconf_unknown_element(cbret, "application", xml_name(xt), NULL) < 0) + goto done; + goto fail; + } + if (ys != NULL && yang_config(ys) != 0){ /* Node-specific validation */ switch (ys->ys_keyword){ + case Y_ANYXML: + case Y_ANYDATA: + goto ok; + break; case Y_LEAF: /* fall thru */ case Y_LEAF_LIST: @@ -520,11 +590,11 @@ xml_yang_validate_all(cxobj *xt, */ if ((yc = yang_find((yang_node*)ys, Y_TYPE, NULL)) != NULL){ if (strcmp(yc->ys_argument, "leafref") == 0){ - if (validate_leafref(xt, yc) < 0) + if (validate_leafref(xt, yc, cbret) < 0) goto done; } else if (strcmp(yc->ys_argument, "identityref") == 0){ - if (validate_identityref(xt, ys, yc) < 0) + if (validate_identityref(xt, ys, yc, cbret) < 0) goto done; } } @@ -568,11 +638,11 @@ xml_yang_validate_all(cxobj *xt, if ((nr = xpath_vec_bool(xt, "%s", xpath)) < 0) goto done; if (!nr){ - if ((ye = yang_find((yang_node*)yc, Y_ERROR_MESSAGE, NULL)) != NULL) - clicon_err(OE_DB, 0, "%s", ye->ys_argument); - else - clicon_err(OE_DB, 0, "xpath %s validation failed", xml_name(xt)); - goto done; + ye = yang_find((yang_node*)yc, Y_ERROR_MESSAGE, NULL); + if (netconf_operation_failed(cbret, "application", + ye?ye->ys_argument:"must xpath validation failed") < 0) + goto done; + goto fail; } } /* "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */ @@ -581,14 +651,42 @@ xml_yang_validate_all(cxobj *xt, if ((nr = xpath_vec_bool(xt, "%s", xpath)) < 0) goto done; if (!nr){ - clicon_err(OE_DB, 0, "xpath %s validation failed", xml_name(xt)); - goto done; + if (netconf_operation_failed(cbret, "application", + "when xpath validation failed") < 0) + goto done; + goto fail; } } } - retval = 0; + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if ((ret = xml_yang_validate_all(x, cbret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + ok: + retval = 1; done: return retval; + fail: + retval = 0; + goto done; +} + +int +xml_yang_validate_all_top(cxobj *xt, + cbuf *cbret) +{ + int ret; + cxobj *x; + + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if ((ret = xml_yang_validate_all(x, cbret)) < 1) + return ret; + } + return 1; } /*! Translate a single xml node to a cligen variable vector. Note not recursive @@ -1310,6 +1408,11 @@ xml_tree_prune_flagged(cxobj *xt, /*! Add default values (if not set) * @param[in] xt XML tree with some node marked + * @param[in] arg Ignored + * Typically called in a recursive apply function: + * @code + * xml_apply(xt, CX_ELMNT, xml_default, NULL); + * @endcode */ int xml_default(cxobj *xt, @@ -1328,7 +1431,8 @@ xml_default(cxobj *xt, goto done; } /* Check leaf defaults */ - if (ys->ys_keyword == Y_CONTAINER || ys->ys_keyword == Y_LIST){ + if (ys->ys_keyword == Y_CONTAINER || ys->ys_keyword == Y_LIST || + ys->ys_keyword == Y_INPUT){ for (i=0; iys_len; i++){ y = ys->ys_stmt[i]; if (y->ys_keyword != Y_LEAF) @@ -1493,9 +1597,9 @@ xml_spec_populate_rpc(clicon_handle h, yang_stmt *y=NULL; /* yang node */ yang_stmt *ymod=NULL; /* yang module */ yang_stmt *yi = NULL; /* input */ - yang_stmt *ya = NULL; /* arg */ + // yang_stmt *ya = NULL; /* arg */ cxobj *x; - cxobj *xi; + // cxobj *xi; int i; if ((strcmp(xml_name(xrpc), "rpc"))!=0){ @@ -1521,11 +1625,18 @@ xml_spec_populate_rpc(clicon_handle h, if (y){ xml_spec_set(x, y); if ((yi = yang_find((yang_node*)y, Y_INPUT, NULL)) != NULL){ + /* kludge rpc -> input */ + xml_spec_set(x, yi); +#if 1 + if (xml_apply(x, CX_ELMNT, xml_spec_populate, yspec) < 0) + goto done; +#else xi = NULL; while ((xi = xml_child_each(x, xi, CX_ELMNT)) != NULL) { if ((ya = yang_find_datanode((yang_node*)yi, xml_name(xi))) != NULL) xml_spec_set(xi, ya); - } + } +#endif } } } diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y index 7072ed01..7600d249 100644 --- a/lib/src/clixon_xml_parse.y +++ b/lib/src/clixon_xml_parse.y @@ -172,7 +172,7 @@ xml_parse_prefixed_name(struct xml_parse_yacc_arg *ya, goto done; if ((x = xml_new(name, xp, y)) == NULL) goto done; - if (xml_namespace_set(x, prefix) < 0) + if (xml_prefix_set(x, prefix) < 0) goto done; ya->ya_xelement = x; retval = 0; @@ -223,9 +223,9 @@ xml_parse_bslash1(struct xml_parse_yacc_arg *ya, xml_name(x), name); goto done; } - if (xml_namespace(x)!=NULL){ + if (xml_prefix(x)!=NULL){ clicon_err(OE_XML, 0, "XML parse sanity check failed: %s:%s vs %s", - xml_namespace(x), xml_name(x), name); + xml_prefix(x), xml_name(x), name); goto done; } /* Strip pretty-print. Ad-hoc algorithm @@ -263,16 +263,16 @@ xml_parse_bslash2(struct xml_parse_yacc_arg *ya, if (strcmp(xml_name(x), name)){ clicon_err(OE_XML, 0, "Sanity check failed: %s:%s vs %s:%s", - xml_namespace(x), + xml_prefix(x), xml_name(x), namespace, name); goto done; } - if (xml_namespace(x)==NULL || - strcmp(xml_namespace(x), namespace)){ + if (xml_prefix(x)==NULL || + strcmp(xml_prefix(x), namespace)){ clicon_err(OE_XML, 0, "Sanity check failed: %s:%s vs %s:%s", - xml_namespace(x), + xml_prefix(x), xml_name(x), namespace, name); @@ -324,7 +324,7 @@ xml_parse_attr(struct xml_parse_yacc_arg *ya, if ((xa = xml_new(name, ya->ya_xelement, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); - if (prefix && xml_namespace_set(xa, prefix) < 0) + if (prefix && xml_prefix_set(xa, prefix) < 0) goto done; if (xml_value_set(xa, attval) < 0) goto done; diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index 7e68d2b8..befb5bcb 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -118,6 +118,9 @@ xml_child_spec(char *name, } else y = NULL; + /* kludge rpc -> input */ + if (y && y->ys_keyword == Y_RPC && yang_find((yang_node*)y, Y_INPUT, NULL)) + y = yang_find((yang_node*)y, Y_INPUT, NULL); *yresult = y; retval = 0; done: diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 8b9af6d0..68c9ce9f 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -862,7 +862,7 @@ ys_module_by_xml(yang_spec *ysp, if (ymodp) *ymodp = NULL; - prefix = xml_namespace(xt); + prefix = xml_prefix(xt); if (prefix){ /* Get namespace for prefix */ if (xml2ns(xt, prefix, &namespace) < 0) diff --git a/test/test_cli.sh b/test/test_cli.sh index 3bf4af77..aa1379d9 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -13,8 +13,10 @@ APPNAME=example . ./lib.sh cfg=$dir/conf_yang.xml +# Use yang in example + cat < $cfg - + $cfg /usr/local/share/$APPNAME/yang /usr/local/share/clixon @@ -115,7 +117,7 @@ expectfn "$clixon_cli -1 -f $cfg -l o debug level 0" 0 "^$" new "cli rpc" expectfn "$clixon_cli -1 -f $cfg -l o rpc ipv4" 0 "ipv4" "2.3.4.5" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_feature.sh b/test/test_feature.sh index 4bbb334c..dfb99172 100755 --- a/test/test_feature.sh +++ b/test/test_feature.sh @@ -96,8 +96,7 @@ new "netconf validate enabled feature" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf disabled feature" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" '^operation-failedapplicationerrorValidation failed]]>]]>$' -#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" '^operation-failedprotocolerrorXML node config/A has no corresponding yang specification (Invalid XML or wrong Yang spec?' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" '^applicationoperation-failederrorValidation failed]]>]]>$' # This test has been broken up into all different modules instead of one large # reply since the modules change so often @@ -162,7 +161,7 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_identity.sh b/test/test_identity.sh index 1acbc764..14d6b996 100755 --- a/test/test_identity.sh +++ b/test/test_identity.sh @@ -162,7 +162,7 @@ new "Set crypto to foo:bar" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo:bar]]>]]>" "^]]>]]>$" new "netconf validate" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorIdentityref validation failed, foo:bar not derived from crypto-alg]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorIdentityref validation failed, foo:bar not derived from crypto-alg]]>]]>$" new "cli set crypto to mc:aes" expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set crypto mc:aes" 0 "^$" @@ -182,7 +182,7 @@ expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set crypto des:des3" 0 "^$" new "cli validate" expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o validate" 0 "^$" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_leafref.sh b/test/test_leafref.sh index 72faaa4e..6e66c7f3 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -105,8 +105,8 @@ expecteof "$clixon_netconf -qf $cfg" 0 'eth3
10.0.4.6
]]>]]>" "^]]>]]>$" -new "leafref validate XXX shouldnt really be operation-failed, more work in validate code" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failed" +new "leafref validate" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationbad-elementeth3errorLeafref validation failed: No such leaf]]>]]>$' new "leafref discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" @@ -126,7 +126,7 @@ new "leafref delete leaf" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth0]]>]]>" "^" new "leafref validate (should fail)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failed" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationbad-elementeth0errorLeafref validation failed: No such leaf]]>]]>$' new "leafref discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" @@ -143,7 +143,7 @@ expectfn "$clixon_cli -1f $cfg -y $fyang -l o set sender a" 0 "^$" new "cli sender template" expectfn "$clixon_cli -1f $cfg -y $fyang -l o set sender b template a" 0 "^$" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_list.sh b/test/test_list.sh index 7e42a23b..37fb8c6c 100755 --- a/test/test_list.sh +++ b/test/test_list.sh @@ -125,7 +125,7 @@ new "minmax: validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" fi # NYI -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_nacm.sh b/test/test_nacm.sh index c22159c2..afd1c29d 100755 --- a/test/test_nacm.sh +++ b/test/test_nacm.sh @@ -141,10 +141,10 @@ new "commit it" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new2 "auth get (no user: access denied)" -expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (wrong passwd: access denied)" -expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (access)" expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} @@ -164,21 +164,21 @@ expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x ' new2 "guest get nacm" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "admin edit nacm" expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" new2 "limited edit nacm" -expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' +expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' new2 "guest edit nacm" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index 1db3949f..2701b2da 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -170,10 +170,10 @@ new "Set x to 0" expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 0}' http://localhost/restconf/data/x)" "" new2 "auth get (no user: access denied)" -expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (wrong passwd: access denied)" -expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (access)" expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} @@ -188,16 +188,16 @@ expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x ' new2 "guest get nacm" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "admin edit nacm" expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" new2 "limited edit nacm" -expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' +expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' new2 "guest edit nacm" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "cli show conf as admin" expectfn "$clixon_cli -1 -U andy -l o -f $cfg -y $fyang show conf" 0 "^x 1;$" @@ -220,7 +220,7 @@ expectfn "$clixon_cli -1 -U guest -l o -f $cfg -y $fyang rpc ipv4" 255 "protocol new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_nacm_protocol.sh b/test/test_nacm_protocol.sh index 2ab70b4b..aebaa843 100755 --- a/test/test_nacm_protocol.sh +++ b/test/test_nacm_protocol.sh @@ -147,7 +147,7 @@ sudo pkill -u www-data -f "/www-data/clixon_restconf" sleep 1 new "start restconf daemon (-a is enable basic authentication)" -sudo su -c "$clixon_restconf -f $cfg -y $fyang -D 1 -- -a" -s /bin/sh www-data & +sudo su -c "$clixon_restconf -f $cfg -y $fyang -- -a" -s /bin/sh www-data & sleep $RCWAIT @@ -166,20 +166,20 @@ expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x" # Rule 1: deny-kill-session new "deny-kill-session: limited fail (netconf)" -expecteof "$clixon_netconf -qf $cfg -y $fyang -U wilma" 0 "44]]>]]>" "^access-deniedprotocolerroraccess denied]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang -U wilma" 0 "44]]>]]>" "^protocolaccess-deniederroraccess denied]]>]]>$" new "deny-kill-session: guest fail (netconf)" -expecteof "$clixon_netconf -qf $cfg -y $fyang -U guest" 0 "44]]>]]>" "^access-deniedprotocolerroraccess denied]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang -U guest" 0 "44]]>]]>" "^protocolaccess-deniederroraccess denied]]>]]>$" new "deny-kill-session: admin ok (netconf)" expecteof "$clixon_netconf -qf $cfg -y $fyang -U andy" 0 "44]]>]]>" "^]]>]]>$" # Rule 2: deny-delete-config new "deny-delete-config: limited fail (netconf)" -expecteof "$clixon_netconf -qf $cfg -y $fyang -U wilma" 0 "]]>]]>" "^access-deniedprotocolerroraccess denied]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang -U wilma" 0 "]]>]]>" "^protocolaccess-deniederroraccess denied]]>]]>$" new2 "deny-delete-config: guest fail (restconf)" -expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' +expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' # In restconf delete-config is translated to edit-config which is permitted new "deny-delete-config: limited fail (restconf) ok" @@ -207,12 +207,12 @@ new "permit-edit-config: limited ok restconf" expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '' new2 "permit-edit-config: guest fail restconf" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_netconf.sh b/test/test_netconf.sh index a0eae7d8..7b060875 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -209,7 +209,7 @@ new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf edit state operation should fail" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "42]]>]]>" "^invalid-value" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "42]]>]]>" "^protocolinvalid-value" new "netconf get state operation" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^42]]>]]>$" @@ -254,7 +254,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" new "netconf client-side rpc" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "example]]>]]>" "^ok]]>]]>$" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_order.sh b/test/test_order.sh index a06a036b..cb9eb780 100755 --- a/test/test_order.sh +++ b/test/test_order.sh @@ -139,7 +139,7 @@ new "get each ordered-by user leaf-list" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^bbar]]>]]>$" new "delete candidate" -expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 'none]]>]]>' "^]]>]]>$" # LEAF_LISTS @@ -172,7 +172,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^cbarbfooafie]]>]]>$" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_perf.sh b/test/test_perf.sh index d03fcbde..66c7a5b1 100755 --- a/test/test_perf.sh +++ b/test/test_perf.sh @@ -173,7 +173,7 @@ expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "< new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_restconf.sh b/test/test_restconf.sh index f98b87e4..03d20bd9 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -52,14 +52,6 @@ module example{ } rpc empty { } - rpc input { - input { - } - } - rpc output { - output { - } - } rpc client-rpc { description "Example local client-side rpc"; input { @@ -104,12 +96,15 @@ new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf new "start restconf daemon" -sudo su -c "$clixon_restconf -f $cfg -y $fyang -D 1" -s /bin/sh www-data & +sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG" -s /bin/sh www-data & sleep $RCWAIT new "restconf tests" +new2 "restconf rpc using POST json without mandatory element" +expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "wrongelement"},"error-severity": "error"}}} ' + new2 "restconf root discovery. RFC 8040 3.1 (xml+xrd)" expecteq "$(curl -s -X GET http://localhost/.well-known/host-meta)" " @@ -125,12 +120,12 @@ expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/r ' new2 "restconf get restconf/operations. RFC8040 3.3.2 (json)" -expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"example:empty": null,"example:input": null,"example:output": null,"example:client-rpc": null,"ietf-routing:fib-route": null,"ietf-routing:route-count": null}} +expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"example:client-rpc": null,"ietf-routing:fib-route": null,"ietf-routing:route-count": null,"example:empty": null}} ' new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) -expect='' +expect='' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" @@ -161,6 +156,9 @@ expectfn "curl -s -I http://localhost/restconf/data" 0 "HTTP/1.1 200 OK" new "restconf empty rpc" expecteq "$(curl -s -X POST -d {\"input\":null} http://localhost/restconf/operations/example:empty)" "" +new2 "restconf empty rpc with extra args (should fail)" +expecteq "$(curl -s -X POST -d {\"input\":{\"extra\":null}} http://localhost/restconf/operations/example:empty)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}} ' + new2 "restconf get empty config + state json" expecteq "$(curl -sSG http://localhost/restconf/data/state)" '{"state": {"op": "42"}} ' @@ -170,7 +168,7 @@ expecteq "$(curl -sSG http://localhost/restconf/data/example:state)" '{"state": ' new2 "restconf get empty config + state json with wrong module name" -expecteq "$(curl -sSG http://localhost/restconf/data/badmodule:state)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "No yang node found: badmodule:state"}}}} ' +expecteq "$(curl -sSG http://localhost/restconf/data/badmodule:state)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "No yang node found: badmodule:state"}}} ' new "restconf get empty config + state xml" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/state) @@ -215,10 +213,10 @@ new "restconf Add subtree to datastore using POST" expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 'HTTP/1.1 200 OK' new "restconf Re-add subtree which should give error" -expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' +expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' # XXX Cant get this to work -#expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"ex:eth\",\"enabled\":true}}} http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' +#expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"ex:eth\",\"enabled\":true}}} http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' new "restconf Check interfaces eth/0/0 added" expectfn "curl -s -G http://localhost/restconf/data" 0 '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "ex:eth","enabled": true}\]},"state": {"op": "42"}} @@ -244,13 +242,13 @@ expecteq "$(curl -s -G http://localhost/restconf/data/state)" '{"state": {"op": ' new2 "restconf Re-post eth/0/0 which should generate error" -expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' +expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new "Add leaf description using POST" expecteq "$(curl -s -X POST -d '{"description":"The-first-interface"}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" new "Add nothing using POST" -expectfn 'curl -s -X POST http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "malformed-message","error-type": "rpc","error-severity": "error","error-message": " on line 1: syntax error at or before:' +expectfn 'curl -s -X POST http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": " on line 1: syntax error at or before:' new2 "restconf Check description added" expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "ex:eth","enabled": true}]}} @@ -263,7 +261,7 @@ new "Check deleted eth/0/0" expectfn 'curl -s -G http://localhost/restconf/data' 0 $state new2 "restconf Re-Delete eth/0/0 using none should generate error" -expecteq "$(curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-missing","error-type": "application","error-severity": "error","error-message": "Data does not exist; cannot delete resource"}}} ' +expecteq "$(curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-missing","error-severity": "error","error-message": "Data does not exist; cannot delete resource"}}} ' new "restconf Add subtree eth/0/0 using PUT" expecteq "$(curl -s -X PUT -d '{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" @@ -273,37 +271,38 @@ expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces ' new2 "restconf rpc using POST json" -expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"}}}} +expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4","destination-address":{"address-family":"ipv6"}}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"},"source-protocol": "static"}}} ' # Cant get this to work due to quoting #new2 "restconf rpc using POST wrong JSON" -#expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":ipv4}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": " on line 1: syntax error at or before: i"}}}} ' +#expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":ipv4}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": " on line 1: syntax error at or before: i"}}} ' -new2 "restconf rpc using POST json w/o mandatory element" -expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}} ' -new2 "restconf rpc non-existing rpc w/o namespace" -expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}} ' +new2 "restconf rpc using POST json without mandatory element" +expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "wrongelement"},"error-severity": "error"}}} ' + +new2 "restconf rpc non-existing rpc without namespace" +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang node not found"}}} ' new2 "restconf rpc non-existing rpc" -expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/example:kalle)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}} ' +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/example:kalle)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang node not found"}}} ' new2 "restconf rpc missing name" -expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Operation name expected"}}}} ' +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "Operation name expected"}}} ' new2 "restconf rpc missing input" -expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}} ' +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "routing-instance-name"},"error-severity": "error","error-message": "Mandatory variable"}}} ' new "restconf rpc using POST xml" ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route) -expect="ipv42.3.4.5" +expect="ipv42.3.4.5static" match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new2 "restconf rpc using wrong prefix" -expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang module not found"}}}} ' +expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' new "restconf local client rpc using POST xml" ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"request":"example"}}' http://localhost/restconf/operations/example:client-rpc) @@ -321,7 +320,7 @@ fi new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index dbddee09..ee820082 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -84,16 +84,16 @@ new "restconf GET if-type" expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0/type" 0 '{"type": "regular"}' new "restconf POST interface without mandatory type" -expectfn 'curl -s -X POST -d {"interface":{"name":"TEST"}} http://localhost/restconf/data/cont1' 0 '"error-message": "Missing mandatory variable: type"' +expectfn 'curl -s -X POST -d {"interface":{"name":"TEST"}} http://localhost/restconf/data/cont1' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "type"},"error-severity": "error","error-message": "Mandatory variable"}}} ' new "restconf POST interface" expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' 0 "" new2 "restconf POST again" -expecteq "$(curl -s -X POST -d '{"interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/cont1)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' +expecteq "$(curl -s -X POST -d '{"interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/cont1)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new2 "restconf POST from top" -expecteq "$(curl -s -X POST -d '{"cont1":{"interface":{"name":"TEST","type":"eth0"}}}' http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' +expecteq "$(curl -s -X POST -d '{"cont1":{"interface":{"name":"TEST","type":"eth0"}}}' http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new "restconf DELETE" expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' 0 "" @@ -138,12 +138,12 @@ new "restconf PUT add interface" expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' 0 "" new "restconf PUT change key error" -expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' 0 '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "api-path keys do not match data keys"}}}}' +expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "api-path keys do not match data keys"}}}' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_rpc.sh b/test/test_rpc.sh new file mode 100755 index 00000000..b1b04251 --- /dev/null +++ b/test/test_rpc.sh @@ -0,0 +1,125 @@ +#!/bin/bash +# RPC tests +# Validate parameters in restconf and netconf, check namespaces, etc +# See rfc8040 3.6 +APPNAME=example + +# include err() and new() functions and creates $dir +. ./lib.sh +cfg=$dir/conf.xml + +# Use yang in example +cat < $cfg + + $cfg + /usr/local/share/$APPNAME/yang + /usr/local/share/clixon + $APPNAME + /usr/local/lib/$APPNAME/backend + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + false + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + 1 + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + +EOF + +new "test params: -f $cfg" +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" + sudo $clixon_backend -s init -f $cfg -D $DBG + if [ $? -ne 0 ]; then + err + fi +fi + +new "kill old restconf daemon" +sudo pkill -u www-data clixon_restconf + +new "start restconf daemon" +sudo su -c "$clixon_restconf -f $cfg" -s /bin/sh www-data & + +sleep $RCWAIT + +new "rpc tests" + +# 1.First some positive tests vary media types +# +new2 "restconf example rpc json/json default - no headers" +expecteq "$(curl -s -X POST -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "42"}} + ' + +new2 "restconf example rpc json/json change y default" +expecteq "$(curl -s -X POST -d '{"input":{"x":"0","y":"99"}}' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "99"}} + ' + +new2 "restconf example rpc json/json" +# XXX example:input example:output +expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+json' -H 'Content-Type: application/yang-data+json' -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "42"}} + ' + +new2 "restconf example rpc xml/json" +expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' -H 'Content-Type: application/yang-data+json' -d '0' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "42"}} + ' + +new2 "restconf example rpc json/xml" +# 042 + ' + +new2 "restconf example rpc xml/xml" +# 0' http://localhost/restconf/operations/example:example)" '042 + ' + +new "netconf example rpc" +expecteof "$clixon_netconf -qf $cfg" 0 '0]]>]]>' '^042]]>]]>$' + +# 2. Then error cases +# +new2 "restconf omit mandatory" +expecteq "$(curl -s -X POST -d '{"input":null}' http://localhost/restconf/operations/example:example)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "x"},"error-severity": "error","error-message": "Mandatory variable"}}} ' + +new2 "restconf add extra" +expecteq "$(curl -s -X POST -d '{"input":{"x":"0","extra":"0"}}' http://localhost/restconf/operations/example:example)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}} ' + +new2 "restconf wrong method" +expecteq "$(curl -s -X POST -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:wrong)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang node not found"}}} ' + +new2 "restconf edit-config missing mandatory" +expecteq "$(curl -s -X POST -d '{"input":null}' http://localhost/restconf/operations/ietf-netconf:edit-config)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' + +new "netconf kill-session missing session-id mandatory" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationmissing-elementsession-iderrorMandatory variable$' + +# edit-config? + +new "Kill restconf daemon" +sudo pkill -u www-data -f "/www-data/clixon_restconf" + +if [ $BE -eq 0 ]; then + exit # BE +fi + +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 +sudo clixon_backend -z -f $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi + +rm -rf $dir diff --git a/test/test_stream.sh b/test/test_stream.sh index 910a29c8..19ad9802 100755 --- a/test/test_stream.sh +++ b/test/test_stream.sh @@ -150,10 +150,10 @@ new "netconf EXAMPLE subscription with filter classifier" expectwait "$clixon_netconf -qf $cfg -y $fyang" "EXAMPLE]]>]]>" '^]]>]]>20' $NCWAIT new "netconf NONEXIST subscription" -expectwait "$clixon_netconf -qf $cfg -y $fyang" 'NONEXIST]]>]]>' '^invalid-valueapplicationerrorNo such stream]]>]]>$' $NCWAIT +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'NONEXIST]]>]]>' '^applicationinvalid-valueerrorNo such stream]]>]]>$' $NCWAIT new "netconf EXAMPLE subscription with wrong date" -expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLEkallekaka]]>]]>' '^bad-elementapplicationstartTimeerrorExpected timestamp]]>]]>$' 0 +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLEkallekaka]]>]]>' '^applicationbad-elementstartTimeerrorExpected timestamp]]>]]>$' 0 #new "netconf EXAMPLE subscription with replay" #NOW=$(date +"%Y-%m-%dT%H:%M:%S") @@ -176,7 +176,7 @@ sleep 2 # Restconf stream subscription RFC8040 Sec 6.3 # Start Subscription w error new "restconf monitor event nonexist stream" -expectwait 'curl -s -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" http://localhost/streams/NOTEXIST' 0 'invalid-valueapplicationerrorNo such stream' 2 +expectwait 'curl -s -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" http://localhost/streams/NOTEXIST' 0 'applicationinvalid-valueerrorNo such stream' 2 # 2a) start subscription 8s - expect 1-2 notifications new "2a) start subscriptions 8s - expect 1-2 notifications" @@ -278,7 +278,7 @@ sleep 5 new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_type.sh b/test/test_type.sh index 4ff09bbc..1261d2ba 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -217,7 +217,7 @@ new "netconf set transitive string error" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "9xx]]>]]>" "^]]>]]>" new "netconf validate should fail" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorvalidation of talle failed regexp match fail" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" 'applicationbad-elementtalleerrorregexp match fail: "9xx" does not match \[a-z\]\[0-9\]\*]]>]]>$' new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -244,7 +244,7 @@ new "netconf set transitive union error int" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "55]]>]]>" "^]]>]]>" new "netconf validate should fail" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorvalidation of ulle failed" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationbad-elementulleerror'55' does not match enumeration]]>]]>$" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -303,7 +303,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^directr2staticr1]]>]]>$" new "when: validate fail" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorxpath static-routes validation failed]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorwhen xpath validation failed]]>]]>$" new "when: discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -133,15 +133,15 @@ new "must: add atm interface" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "atm32]]>]]>" "^]]>]]>$" new "must: atm validate fail" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorAn ATM MTU must be 64 .. 17966]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorAn ATM MTU must be 64 .. 17966]]>]]>$" new "must: add eth interface" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ethernet989]]>]]>" "^]]>]]>$" new "must: eth validate fail" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorAn Ethernet MTU must be 1500]]>]]>" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorAn Ethernet MTU must be 1500]]>]]>" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_yang.sh b/test/test_yang.sh index 9a3f7cfe..1d953dc5 100755 --- a/test/test_yang.sh +++ b/test/test_yang.sh @@ -212,7 +212,7 @@ expectfn "$clixon_cli -1f $cfg -y $fyang set x f e foo" 0 "" new "cli show leaf-list" expectfn "$clixon_cli -1f $cfg -y $fyang show xpath /x/f/e" 0 "foo" new "netconf set state data (not allowed)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "42]]>]]>" "^invalid-value" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "42]]>]]>" "^protocolinvalid-value" new "netconf set presence and not present" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -233,7 +233,7 @@ new "netconf validate anyxml" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf delete candidate" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'none]]>]]>' "^]]>]]>$" # Check 3-keys new "netconf add one 3-key entry" @@ -262,7 +262,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'none]]>]]>' "^]]>]]>$" new "netconf commit empty candidate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -279,7 +279,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_yang_load.sh b/test/test_yang_load.sh index 9bcae5fa..90fc10e4 100755 --- a/test/test_yang_load.sh +++ b/test/test_yang_load.sh @@ -88,12 +88,12 @@ new "1. Set newex" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set oldex should fail (since oldex is in old revision and only the new is loaded)" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedapplicationerrorValidation failed]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederrorValidation failed]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedapplicationerrorValidation failed]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederrorValidation failed]]>]]>$' -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi @@ -140,10 +140,10 @@ new "Set oldex" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederrorValidation failed]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederrorValidation failed]]>]]>$' new "Kill backend" # Check if premature kill @@ -184,10 +184,10 @@ new "Set newex" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set oldex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' new "Kill backend" # Check if premature kill @@ -229,10 +229,10 @@ new "Set oldex" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' new "Kill backend" # Check if premature kill @@ -273,7 +273,7 @@ new "Set newex" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set oldex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' new "Set other" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' @@ -318,7 +318,7 @@ new "Set oldex" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' new "Set other" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' @@ -365,7 +365,7 @@ new "Set oldex" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' new "Set other" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' @@ -411,10 +411,10 @@ new "Set oldex" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' new "Kill backend" # Check if premature kill diff --git a/test/test_yang_namespace.sh b/test/test_yang_namespace.sh index 215e91f3..19447f80 100755 --- a/test/test_yang_namespace.sh +++ b/test/test_yang_namespace.sh @@ -122,7 +122,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " Date: Fri, 21 Dec 2018 14:44:59 +0100 Subject: [PATCH 03/17] netconf error handling and test summary script --- CHANGELOG.md | 3 +- apps/netconf/clixon_netconf.h | 1 + apps/netconf/netconf_lib.c | 37 ++++- apps/netconf/netconf_lib.h | 1 + apps/netconf/netconf_main.c | 9 +- apps/netconf/netconf_rpc.c | 8 +- apps/restconf/restconf_lib.c | 2 + apps/restconf/restconf_methods.c | 11 +- example/example.yang | 55 ++++++ lib/clixon/clixon_netconf_lib.h | 3 + lib/src/clixon_netconf_lib.c | 277 ++++++++++++++++--------------- lib/src/clixon_xml_map.c | 18 +- test/README.md | 17 +- test/all.sh | 40 ++++- test/lib.sh | 12 +- test/test_nacm.sh | 2 +- test/test_nacm_ext.sh | 2 +- test/test_nacm_protocol.sh | 2 +- test/test_perf.sh | 2 +- test/test_restconf.sh | 4 +- test/test_restconf2.sh | 2 +- test/test_rpc.sh | 8 +- test/test_stream.sh | 4 +- 23 files changed, 331 insertions(+), 189 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0035a93d..ce4188a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,8 @@ * Openconfig yang specs parsed: https://github.com/openconfig/public * Improved "unknown" handling * More precise Yang validation and better error messages - * For Example, adding bad-, missing-, or unknown-element error messages, etc instead of operation-failed + * Example: adding bad-, missing-, or unknown-element error messages, etc instead of operation-failed, bad-element instead of "yang node not found", etc. + * * Yang load file configure options changed * `CLICON_YANG_DIR` is changed from a single directory to a path of directories * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list diff --git a/apps/netconf/clixon_netconf.h b/apps/netconf/clixon_netconf.h index e2fbb5bb..9fed0a1a 100644 --- a/apps/netconf/clixon_netconf.h +++ b/apps/netconf/clixon_netconf.h @@ -44,6 +44,7 @@ * (Duplicated. Also in netconf_*.h) */ int netconf_output(int s, cbuf *xf, char *msg); +int netconf_output_encap(int s, cbuf *xf, char *msg); int netconf_xpath(cxobj *xsearch, cxobj *xfilter, diff --git a/apps/netconf/netconf_lib.c b/apps/netconf/netconf_lib.c index fc24810b..50723de4 100644 --- a/apps/netconf/netconf_lib.c +++ b/apps/netconf/netconf_lib.c @@ -182,9 +182,9 @@ netconf_get_target(cxobj *xn, * @param[in] s * @param[in] cb Cligen buffer that contains the XML message * @param[in] msg Only for debug - * @note Assumes "cb" contains valid XML, ie encoding is correct. This is done - * if it is output by a xml render routine (xml_print et al), but NOT - * otherwise. + * @retval 0 OK + * @retval -1 Error + * @see netconf_output_encap for function with encapsulation */ int netconf_output(int s, @@ -216,3 +216,34 @@ netconf_output(int s, return retval; } + +/*! Encapsulate and send outgoing netconf packet as cbuf on socket + * @param[in] s + * @param[in] cb Cligen buffer that contains the XML message + * @param[in] msg Only for debug + * @retval 0 OK + * @retval -1 Error + * @note Assumes "cb" contains valid XML + * @see netconf_output without encapsulation + */ +int +netconf_output_encap(int s, + cbuf *cb, + char *msg) +{ + int retval = -1; + cbuf *cb1 = NULL; + + if ((cb1 = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + add_preamble(cb1); + cprintf(cb1, "%s", cbuf_get(cb)); + add_postamble(cb1); + retval = netconf_output(s, cb1, msg); + done: + if (cb1) + cbuf_free(cb1); + return retval; +} diff --git a/apps/netconf/netconf_lib.h b/apps/netconf/netconf_lib.h index f2dc73e1..6d2de111 100644 --- a/apps/netconf/netconf_lib.h +++ b/apps/netconf/netconf_lib.h @@ -75,5 +75,6 @@ int add_error_preamble(cbuf *xf, char *reason); char *netconf_get_target(cxobj *xn, char *path); int add_error_postamble(cbuf *xf); int netconf_output(int s, cbuf *xf, char *msg); +int netconf_output_encap(int s, cbuf *cb, char *msg); #endif /* _NETCONF_LIB_H_ */ diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index cc4c9710..dc57126e 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -112,7 +112,7 @@ process_incoming_packet(clicon_handle h, free(str0); if (netconf_operation_failed(cbret, "rpc", "internal error")< 0) goto done; - netconf_output(1, cbret, "rpc-error"); + netconf_output_encap(1, cbret, "rpc-error"); goto done; } free(str0); @@ -121,7 +121,7 @@ process_incoming_packet(clicon_handle h, if ((ret = xml_yang_validate_rpc(xrpc, cbret)) < 0) goto done; if (ret == 0){ - netconf_output(1, cbret, "rpc-error"); + netconf_output_encap(1, cbret, "rpc-error"); goto ok; } } @@ -157,11 +157,8 @@ process_incoming_packet(clicon_handle h, if (xml_addsub(xc, xa2) < 0) goto done; } - add_preamble(cbret); - clicon_xml2cbuf(cbret, xml_child_i(xret,0), 0, 0); - add_postamble(cbret); - if (netconf_output(1, cbret, "rpc-reply") < 0){ + if (netconf_output_encap(1, cbret, "rpc-reply") < 0){ cbuf_free(cbret); goto done; } diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 64001137..4d9b354a 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -776,12 +776,10 @@ netconf_notification_cb(int s, clicon_err(OE_PLUGIN, errno, "cbuf_new"); goto done; } - add_preamble(cb); /* Make it well-formed netconf xml */ if (clicon_xml2cbuf(cb, xn, 0, 0) < 0) goto done; - add_postamble(cb); /* Send it to listening client on stdout */ - if (netconf_output(1, cb, "notification") < 0){ + if (netconf_output_encap(1, cb, "notification") < 0){ cbuf_free(cb); goto done; } @@ -925,13 +923,13 @@ netconf_application_rpc(clicon_handle h, if ((ret = xml_yang_validate_all_top(xn, cbret)) < 0) goto done; if (ret == 0){ - netconf_output(1, cbret, "rpc-error"); + netconf_output_encap(1, cbret, "rpc-error"); goto ok; } if ((ret = xml_yang_validate_add(xn, cbret)) < 0) goto done; if (ret == 0){ - netconf_output(1, cbret, "rpc-error"); + netconf_output_encap(1, cbret, "rpc-error"); goto ok; } } diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index be214fbb..062dff65 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -450,10 +450,12 @@ api_return_err(clicon_handle h, else if (xml2json_cbuf(cb, xerr, pretty) < 0) goto done; + clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb)); FCGX_SetExitStatus(code, r->out); /* Created */ FCGX_FPrintF(r->out, "Status: %d %s\r\n", code, reason_phrase); FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n\r\n", use_xml?"xml":"json"); + if (use_xml){ if (pretty){ FCGX_FPrintF(r->out, " \n", cbuf_get(cb)); diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index b8beb068..a11d55c1 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -1118,15 +1118,8 @@ api_operations_post(clicon_handle h, goto ok; } if ((yrpc = yang_find((yang_node*)ys, Y_RPC, id)) == NULL){ - if (netconf_operation_failed_xml(&xerr, "protocol", "yang node not found") < 0) - goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - if (yrpc == NULL){ - if (netconf_operation_failed_xml(&xerr, "protocol", "yang node not found") < 0) + clicon_debug(1, "%s rpc %s not found", __FUNCTION__, id); + if (netconf_missing_element_xml(&xerr, "application", id, "RPC not defined") < 0) goto done; if ((xe = xpath_first(xerr, "rpc-error")) != NULL) if (api_return_err(h, r, xe, pretty, use_xml) < 0) diff --git a/example/example.yang b/example/example.yang index 57f58fcb..749b10ff 100644 --- a/example/example.yang +++ b/example/example.yang @@ -26,6 +26,10 @@ module example { } /* Translation function example - See also example_cli */ list translate{ + key k; + leaf k{ + type string; + } leaf value{ type string; } @@ -73,6 +77,57 @@ module example { } } } + rpc empty { + description "Smallest possible RPC with no input or output"; + } + rpc example { + description "Some example input/output for testing RFC7950 7.14. + RPC simply echoes the input for debugging."; + input { + leaf x { + description + "If a leaf in the input tree has a 'mandatory' statement with + the value 'true', the leaf MUST be present in an RPC invocation."; + type string; + mandatory true; + } + leaf y { + description + "If a leaf in the input tree has a 'mandatory' statement with the + value 'true', the leaf MUST be present in an RPC invocation."; + type string; + default "42"; + } + leaf-list z { + description + "If a leaf-list in the input tree has one or more default + values, the server MUST use these values (XXX not supported)"; + type string; + } + leaf w { + description + "If any node has a 'when' statement that would evaluate to + 'false',then this node MUST NOT be present in the input tree. + (XXX not supported)"; + type string; + when "/translate/k=5/value='w'"; + } + } + output { + leaf x { + type string; + } + leaf y { + type string; + } + leaf z { + type string; + } + leaf w { + type string; + } + } + } rpc debug { description "Set debug level of backend. XXX should be in clixon-config"; input { diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index b22e697b..8f1b9da0 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -47,8 +47,11 @@ int netconf_missing_attribute(cbuf *cb, char *type, char *info, char *message); int netconf_bad_attribute(cbuf *cb, char *type, char *info, char *message); int netconf_unknown_attribute(cbuf *cb, char *type, char *info, char *message); int netconf_missing_element(cbuf *cb, char *type, char *element, char *message); +int netconf_missing_element_xml(cxobj **xret, char *type, char *element, char *message); int netconf_bad_element(cbuf *cb, char *type, char *info, char *element); +int netconf_bad_element_xml(cxobj **xret, char *type, char *info, char *element); int netconf_unknown_element(cbuf *cb, char *type, char *element, char *message); +int netconf_unknown_element_xml(cxobj **xret, char *type, char *element, char *message); int netconf_unknown_namespace(cbuf *cb, char *type, char *info, char *message); int netconf_access_denied(cbuf *cb, char *type, char *message); int netconf_access_denied_xml(cxobj **xret, char *type, char *message); diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index 6c284fc6..8db94c4a 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -307,6 +307,45 @@ netconf_unknown_attribute(cbuf *cb, goto done; } +/*! Common Netconf element XML tree according to RFC 6241 App A + * @param[out] xret Error XML tree. Free with xml_free after use + * @param[in] type Error type: "application" or "protocol" + * @param[in] tag Error tag + * @param[in] element bad-element xml + * @param[in] message Error message + */ +static int +netconf_element_xml_common(cxobj **xret, + char *type, + char *tag, + char *element, + char *message) +{ + int retval =-1; + cxobj *xerr; + + if (*xret == NULL){ + if ((*xret = xml_new("rpc-reply", NULL, NULL)) == NULL) + goto done; + } + else if (xml_name_set(*xret, "rpc-reply") < 0) + goto done; + if ((xerr = xml_new("rpc-error", *xret, NULL)) == NULL) + goto done; + if (xml_parse_va(&xerr, NULL, "%s" + "%s" + "%s" + "error", + type, tag, element) < 0) + goto done; + if (message && xml_parse_va(&xerr, NULL, "%s", + message) < 0) + goto done; + retval = 0; + done: + return retval; +} + /*! Create Netconf missing-element error XML tree according to RFC 6241 App A * * An expected element is missing. @@ -321,32 +360,34 @@ netconf_missing_element(cbuf *cb, char *element, char *message) { - int retval = -1; - char *encstr = NULL; + int retval = -1; + cxobj *xret = NULL; - if (cprintf(cb, "" - "%s" - "missing-element" - "%s" - "error", - type, element) <0) - goto err; - if (message){ - if (xml_chardata_encode(&encstr, "%s", message) < 0) - goto done; - if (cprintf(cb, "%s", encstr) < 0) - goto err; - } - if (cprintf(cb, "") <0) - goto err; + if (netconf_element_xml_common(&xret, type, "missing-element", element, message) < 0) + goto done; + if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) + goto done; retval = 0; done: - if (encstr) - free(encstr); + if (xret) + xml_free(xret); return retval; - err: - clicon_err(OE_XML, errno, "cprintf"); - goto done; +} + + +/*! Create Netconf missing-element error XML tree according to RFC 6241 App A + * @param[out] xret Error XML tree. Free with xml_free after use + * @param[in] type Error type: "application" or "protocol" + * @param[in] element bad-element xml + * @param[in] message Error message + */ +int +netconf_missing_element_xml(cxobj **xret, + char *type, + char *element, + char *message) +{ + return netconf_element_xml_common(xret, type, "missing-element", element, message); } /*! Create Netconf bad-element error XML tree according to RFC 6241 App A @@ -364,32 +405,26 @@ netconf_bad_element(cbuf *cb, char *element, char *message) { - int retval = -1; - char *encstr = NULL; + int retval = -1; + cxobj *xret = NULL; - if (cprintf(cb, "" - "%s" - "bad-element" - "%s" - "error", - type, element) <0) - goto err; - if (message){ - if (xml_chardata_encode(&encstr, "%s", message) < 0) - goto done; - if (cprintf(cb, "%s", encstr) < 0) - goto err; - } - if (cprintf(cb, "") <0) - goto err; + if (netconf_element_xml_common(&xret, type, "bad-element", element, message) < 0) + goto done; + if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) + goto done; retval = 0; done: - if (encstr) - free(encstr); + if (xret) + xml_free(xret); return retval; - err: - clicon_err(OE_XML, errno, "cprintf"); - goto done; +} +int +netconf_bad_element_xml(cxobj **xret, + char *type, + char *element, + char *message) +{ + return netconf_element_xml_common(xret, type, "bad-element", element, message); } /*! Create Netconf unknown-element error XML tree according to RFC 6241 App A @@ -406,32 +441,26 @@ netconf_unknown_element(cbuf *cb, char *element, char *message) { - int retval = -1; - char *encstr = NULL; + int retval = -1; + cxobj *xret = NULL; - if (cprintf(cb, "" - "%s" - "unknown-element" - "%s" - "error", - type, element) <0) - goto err; - if (message){ - if (xml_chardata_encode(&encstr, "%s", message) < 0) - goto done; - if (cprintf(cb, "%s", encstr) < 0) - goto err; - } - if (cprintf(cb, "") <0) - goto err; + if (netconf_element_xml_common(&xret, type, "unknown-element", element, message) < 0) + goto done; + if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) + goto done; retval = 0; done: - if (encstr) - free(encstr); + if (xret) + xml_free(xret); return retval; - err: - clicon_err(OE_XML, errno, "cprintf"); - goto done; +} +int +netconf_unknown_element_xml(cxobj **xret, + char *type, + char *element, + char *message) +{ + return netconf_element_xml_common(xret, type, "unknown-element", element, message); } /*! Create Netconf unknown-namespace error XML tree according to RFC 6241 App A @@ -476,53 +505,48 @@ netconf_unknown_namespace(cbuf *cb, goto done; } -/*! Create Netconf access-denied error XML tree according to RFC 6241 App A +/*! Create Netconf access-denied error cbuf according to RFC 6241 App A * * Access to the requested protocol operation or data model is denied because * authorization failed. * @param[out] cb CLIgen buf. Error XML is written in this buffer * @param[in] type Error type: "application" or "protocol" * @param[in] message Error message + * @see netconf_access_denied_xml Same but returns XML tree */ int netconf_access_denied(cbuf *cb, char *type, char *message) { - int retval = -1; - char *encstr = NULL; - - if (cprintf(cb, "" - "%s" - "access-denied" - "error", - type) <0) - goto err; - if (message){ - if (xml_chardata_encode(&encstr, "%s", message) < 0) - goto done; - if (cprintf(cb, "%s", encstr) < 0) - goto err; - } - if (cprintf(cb, "") <0) - goto err; + int retval = -1; + cxobj *xret = NULL; + + if (netconf_access_denied_xml(&xret, type, message) < 0) + goto done; + if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) + goto done; retval = 0; done: - if (encstr) - free(encstr); + if (xret) + xml_free(xret); return retval; - err: - clicon_err(OE_XML, errno, "cprintf"); - goto done; } /*! Create Netconf access-denied error XML tree according to RFC 6241 App A * * Access to the requested protocol operation or data model is denied because * authorization failed. - * @param[out] xret Error XML tree + * @param[out] xret Error XML tree. Free with xml_free after use * @param[in] type Error type: "application" or "protocol" * @param[in] message Error message + * @code + * cxobj *xret = NULL; + * if (netconf_access_denied_xml(&xret, "protocol", "Unauthorized") < 0) + * err; + * xml_free(xret); + * @endcode + * @see netconf_access_denied Same but returns cligen buffer */ int netconf_access_denied_xml(cxobj **xret, @@ -795,37 +819,25 @@ netconf_operation_not_supported(cbuf *cb, * @param[out] cb CLIgen buf. Error XML is written in this buffer * @param[in] type Error type: "rpc", "application" or "protocol" * @param[in] message Error message + * @see netconf_operation_failed_xml Same but returns XML tree */ int netconf_operation_failed(cbuf *cb, char *type, char *message) { - int retval = -1; - char *encstr = NULL; + int retval = -1; + cxobj *xret = NULL; - if (cprintf(cb, "" - "%s" - "operation-failed" - "error", - type) <0) - goto err; - if (message){ - if (xml_chardata_encode(&encstr, "%s", message) < 0) - goto done; - if (cprintf(cb, "%s", encstr) < 0) - goto err; - } - if (cprintf(cb, "") < 0) - goto err; + if (netconf_operation_failed_xml(&xret, type, message) < 0) + goto done; + if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) + goto done; retval = 0; done: - if (encstr) - free(encstr); + if (xret) + xml_free(xret); return retval; - err: - clicon_err(OE_XML, errno, "cprintf"); - goto done; } /*! Create Netconf operation-failed error XML tree according to RFC 6241 App A @@ -835,6 +847,13 @@ netconf_operation_failed(cbuf *cb, * @param[out] xret Error XML tree * @param[in] type Error type: "rpc", "application" or "protocol" * @param[in] message Error message + * @code + * cxobj *xret = NULL; + * if (netconf_operation_failed_xml(&xret, "protocol", "Unauthorized") < 0) + * err; + * xml_free(xret); + * @endcode + * @see netconf_operation_failed Same but returns cligen buffer */ int netconf_operation_failed_xml(cxobj **xret, @@ -872,35 +891,24 @@ netconf_operation_failed_xml(cxobj **xret, * @param[out] cb CLIgen buf. Error XML is written in this buffer * @param[in] message Error message * @note New in :base:1.1 + * @see netconf_malformed_message_xml Same but returns XML tree */ int netconf_malformed_message(cbuf *cb, char *message) { int retval = -1; - char *encstr = NULL; + cxobj *xret = NULL; - if (cprintf(cb, "" - "rpc" - "malformed-message" - "error") <0) - goto err; - if (message){ - if (xml_chardata_encode(&encstr, "%s", message) < 0) - goto done; - if (cprintf(cb, "%s", encstr) < 0) - goto err; - } - if (cprintf(cb, "") <0) - goto err; + if (netconf_malformed_message_xml(&xret, message) < 0) + goto done; + if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) + goto done; retval = 0; done: - if (encstr) - free(encstr); + if (xret) + xml_free(xret); return retval; - err: - clicon_err(OE_XML, errno, "cprintf"); - goto done; } /*! Create Netconf malformed-message error XML tree according to RFC 6241 App A @@ -911,10 +919,17 @@ netconf_malformed_message(cbuf *cb, * @param[out] xret Error XML tree * @param[in] message Error message * @note New in :base:1.1 + * @code + * cxobj *xret = NULL; + * if (netconf_malformed_message_xml(&xret, "Unauthorized") < 0) + * err; + * xml_free(xret); + * @endcode + * @see netconf_malformed_message Same but returns cligen buffer */ int netconf_malformed_message_xml(cxobj **xret, - char *message) + char *message) { int retval =-1; cxobj *xerr; diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 2263bde2..2644f494 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -398,6 +398,8 @@ validate_identityref(cxobj *xt, * in [RFC6241]. If output parameters are returned, they are encoded as * child elements to the element defined in [RFC6241], in * the same order as they are defined within the "output" statement. + * @see xml_yang_validate_all + * @note Should need a variant accepting cxobj **xret */ int xml_yang_validate_rpc(cxobj *xrpc, @@ -454,6 +456,7 @@ xml_yang_validate_rpc(cxobj *xrpc, * fail; * @endcode * @see xml_yang_validate_all + * @note Should need a variant accepting cxobj **xret */ int xml_yang_validate_add(cxobj *xt, @@ -503,15 +506,14 @@ xml_yang_validate_add(cxobj *xt, * needs to be reparsed when concrete type is selected */ if ((body = xml_body(xt)) != NULL){ - if (cv_parse(body, cv) <0){ - clicon_err(OE_UNIX, errno, "cv_parse"); - goto done; + if (cv_parse1(body, cv, &reason) != 1){ + if (netconf_bad_element(cbret, "application", ys->ys_argument, reason) < 0) + goto done; + goto fail; } if ((ys_cv_validate(cv, ys, &reason)) != 1){ if (netconf_bad_element(cbret, "application", ys->ys_argument, reason) < 0) goto done; - if (reason) - free(reason); goto fail; } } @@ -531,6 +533,8 @@ xml_yang_validate_add(cxobj *xt, done: if (cv) cv_free(cv); + if (reason) + free(reason); return retval; fail: retval = 0; @@ -544,7 +548,6 @@ xml_yang_validate_add(cxobj *xt, * @retval 1 Validation OK * @retval 0 Validation failed * @retval -1 Error - * @see xml_yang_validate_add * @code * cxobj *x; * cbuf *cbret = cbuf_new(); @@ -553,6 +556,9 @@ xml_yang_validate_add(cxobj *xt, * if (ret == 0) * fail; * @endcode + * @see xml_yang_validate_add + * @see xml_yang_validate_rpc + * @note Should need a variant accepting cxobj **xret */ int xml_yang_validate_all(cxobj *xt, diff --git a/test/README.md b/test/README.md index 7ae292bb..59832663 100644 --- a/test/README.md +++ b/test/README.md @@ -1,9 +1,9 @@ # Clixon tests This directory contains testing code for clixon and the example -routing application. Assumes setup of http daemon as describe under apps/restonf -- clixon A top-level script clones clixon in /tmp and starts all.sh. You can copy this file (review it first) and place as cron script -- all.sh Run through all tests named 'test*.sh' in this directory. Therefore, if you place a test in this directory matching 'test*.sh' it will be run automatically. +application. Assumes setup of http daemon as describe under apps/restonf +- clixon A top-level script clones clixon in /tmp and starts all.sh. You can copy this file (review it first) and place as cron script +- all.sh Run through all tests named 'test*.sh' in this directory. Therefore, if you place a test in this directory matching 'test*.sh' it will be run automatically. By default the script will exit on first error. Run as `all.sh summary` to continue and print a summary on all tests. - test_nacm.sh Auth tests using internal NACM - test_nacm_ext.sh Auth tests using external NACM (separate file) - test_cli.sh CLI tests @@ -13,3 +13,14 @@ routing application. Assumes setup of http daemon as describe under apps/restonf - test_leafref.sh Yang leafref tests - test_datastore.sh Datastore tests +Example runs: +``` +> run.sh +# Runs through all tests matching 'test_*.sh' in the directory. Prints test output +# and stops on first error + +> run.sh summary +# Same as above but continues after errors and does not print test output. +``` + + diff --git a/test/all.sh b/test/all.sh index d0e92410..79a0309f 100755 --- a/test/all.sh +++ b/test/all.sh @@ -1,17 +1,43 @@ #!/bin/bash # Run, eg as: -# ./run.sh 2>&1 | tee test.log +# ./all.sh 2>&1 | tee test.log # break on first test +# ./all.sh summary # to run all tests and print + +summary=0 +if [ $# -gt 0 ]; then + summary=1 +fi +if [ $# -gt 1 ]; then + echo "usage: $0 [summary] # pipe to dev/null and continue on error" + exit -1 +fi # include err() and new() functions . ./lib.sh - +err=0 for test in test*.sh; do echo "Running $test" - ./$test - errcode=$? + if [ $summary -ne 0 ]; then + ./$test > /dev/null 2>&1 + errcode=$? + else + ./$test + errcode=$? + fi if [ $errcode -ne 0 ]; then - echo "Error in $test errcode=$errcode" - exit $errcode + err=1 + echo -e "\e[31mError in $test errcode=$errcode" + echo -ne "\e[0m" + if [ $summary -eq 0 ]; then + exit $errcode + fi fi done -echo OK +if [ $err -eq 0 ]; then + echo OK +else + echo -e "\e[31mError" + echo -ne "\e[0m" +fi + + diff --git a/test/lib.sh b/test/lib.sh index 2a66d2d5..3d82e32d 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -38,7 +38,9 @@ if [ ! -d $dir ]; then fi rm -rf $dir/* -# error and exit, arg is optional extra errmsg +# error and exit, +# arg1: expected +# arg2: errmsg[optional] err(){ echo -e "\e[31m\nError in Test$testnr [$testname]:" if [ $# -gt 0 ]; then @@ -53,7 +55,7 @@ err(){ echo "$expect"| od -t c > $dir/clixon-expect diff $dir/clixon-expect $dir/clixon-ret - exit $testnr + exit -1 #$testnr } # Increment test number and print a nice string @@ -221,8 +223,9 @@ expectwait(){ done # cat /tmp/flag if [ $(cat /tmp/flag) != "ok" ]; then - cat /tmp/flag - exit +# err "ok" $(cat /tmp/flag) +# cat /tmp/flag + exit -1 fi } @@ -250,5 +253,4 @@ expectmatch(){ fi fi fi - } diff --git a/test/test_nacm.sh b/test/test_nacm.sh index afd1c29d..389ae010 100755 --- a/test/test_nacm.sh +++ b/test/test_nacm.sh @@ -123,7 +123,7 @@ sudo pkill -u www-data -f "/www-data/clixon_restconf" sleep 1 new "start restconf daemon (-a is enable basic authentication)" -sudo su -c "$clixon_restconf -f $cfg -y $fyang -- -a" -s /bin/sh www-data & +sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG -- -a" -s /bin/sh www-data & sleep $RCWAIT diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index 2701b2da..9bdee51d 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -155,7 +155,7 @@ new "kill old restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" new "start restconf daemon (-a is enable http basic auth)" -sudo su -c "$clixon_restconf -f $cfg -y $fyang -- -a" -s /bin/sh www-data & +sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG -- -a" -s /bin/sh www-data & sleep $RCWAIT diff --git a/test/test_nacm_protocol.sh b/test/test_nacm_protocol.sh index aebaa843..963218e5 100755 --- a/test/test_nacm_protocol.sh +++ b/test/test_nacm_protocol.sh @@ -147,7 +147,7 @@ sudo pkill -u www-data -f "/www-data/clixon_restconf" sleep 1 new "start restconf daemon (-a is enable basic authentication)" -sudo su -c "$clixon_restconf -f $cfg -y $fyang -- -a" -s /bin/sh www-data & +sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG -- -a" -s /bin/sh www-data & sleep $RCWAIT diff --git a/test/test_perf.sh b/test/test_perf.sh index 66c7a5b1..4d9d6dfc 100755 --- a/test/test_perf.sh +++ b/test/test_perf.sh @@ -78,7 +78,7 @@ new "kill old restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" new "start restconf daemon" -sudo su -c "$clixon_restconf -f $cfg -y $fyang" -s /bin/sh www-data & +sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG" -s /bin/sh www-data & sleep $RCWAIT diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 03d20bd9..1874a0b3 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -282,10 +282,10 @@ new2 "restconf rpc using POST json without mandatory element" expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "wrongelement"},"error-severity": "error"}}} ' new2 "restconf rpc non-existing rpc without namespace" -expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang node not found"}}} ' +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "kalle"},"error-severity": "error","error-message": "RPC not defined"}}} ' new2 "restconf rpc non-existing rpc" -expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/example:kalle)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang node not found"}}} ' +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/example:kalle)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "kalle"},"error-severity": "error","error-message": "RPC not defined"}}} ' new2 "restconf rpc missing name" expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "Operation name expected"}}} ' diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index ee820082..1cf7188f 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -65,7 +65,7 @@ new "kill old restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" new "start restconf daemon" -sudo su -c "$clixon_restconf -f $cfg -y $fyang" -s /bin/sh www-data & +sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG" -s /bin/sh www-data & sleep $RCWAIT diff --git a/test/test_rpc.sh b/test/test_rpc.sh index b1b04251..f28a4df9 100755 --- a/test/test_rpc.sh +++ b/test/test_rpc.sh @@ -46,7 +46,7 @@ new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf new "start restconf daemon" -sudo su -c "$clixon_restconf -f $cfg" -s /bin/sh www-data & +sudo su -c "$clixon_restconf -f $cfg -D $DBG" -s /bin/sh www-data & sleep $RCWAIT @@ -54,7 +54,7 @@ new "rpc tests" # 1.First some positive tests vary media types # -new2 "restconf example rpc json/json default - no headers" +new2 "restconf example rpc json/json default - no http media headers" expecteq "$(curl -s -X POST -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "42"}} ' @@ -93,13 +93,13 @@ new2 "restconf add extra" expecteq "$(curl -s -X POST -d '{"input":{"x":"0","extra":"0"}}' http://localhost/restconf/operations/example:example)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}} ' new2 "restconf wrong method" -expecteq "$(curl -s -X POST -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:wrong)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang node not found"}}} ' +expecteq "$(curl -s -X POST -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:wrong)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "wrong"},"error-severity": "error","error-message": "RPC not defined"}}} ' new2 "restconf edit-config missing mandatory" expecteq "$(curl -s -X POST -d '{"input":null}' http://localhost/restconf/operations/ietf-netconf:edit-config)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' new "netconf kill-session missing session-id mandatory" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationmissing-elementsession-iderrorMandatory variable$' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationmissing-elementsession-iderrorMandatory variable]]>]]>$' # edit-config? diff --git a/test/test_stream.sh b/test/test_stream.sh index 19ad9802..87932744 100755 --- a/test/test_stream.sh +++ b/test/test_stream.sh @@ -121,7 +121,7 @@ new "kill old restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" new "start restconf daemon" -sudo su -c "$clixon_restconf -f $cfg -y $fyang" -s /bin/sh www-data & +sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG" -s /bin/sh www-data & sleep $RCWAIT @@ -153,7 +153,7 @@ new "netconf NONEXIST subscription" expectwait "$clixon_netconf -qf $cfg -y $fyang" 'NONEXIST]]>]]>' '^applicationinvalid-valueerrorNo such stream]]>]]>$' $NCWAIT new "netconf EXAMPLE subscription with wrong date" -expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLEkallekaka]]>]]>' '^applicationbad-elementstartTimeerrorExpected timestamp]]>]]>$' 0 +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLEkallekaka]]>]]>' '^applicationbad-elementstartTimeerrorInvalid time: kallekaka]]>]]>$' 0 #new "netconf EXAMPLE subscription with replay" #NOW=$(date +"%Y-%m-%dT%H:%M:%S") From 0baebc93fdc86388afe3a205ea192ba8d536f1ec Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 2 Jan 2019 15:18:29 +0100 Subject: [PATCH 04/17] * Strict namespace setting can be a problem when upgrading existing database files, such as startup-db or persistent running-db, or any other saved XML file. * For backward compatibility, load of startup and running set CLICON_XML_NS_STRICT to false temporarily. * Added three-valued return values for several validate functions where -1 is fatal error, 0 is validation failed and 1 is validation OK. * This includes: `xmldb_put`, `xml_yang_validate_all`, `xml_yang_validate_add`, `xml_yang_validate_rpc`, `api_path2xml`, `api_path2xpath` * Added new xml functions for specific types: `xml_child_nr_notype`, `xml_child_nr_notype`, `xml_child_i_type`, `xml_find_type`. --- CHANGELOG.md | 75 +- DEVELOP.md | 3 + README.md | 20 + apps/backend/backend_client.c | 70 +- apps/backend/backend_commit.c | 36 +- apps/backend/backend_main.c | 147 +-- apps/backend/backend_plugin.c | 2 +- apps/cli/cli_common.c | 33 +- apps/cli/cli_show.c | 12 +- apps/netconf/netconf_main.c | 2 + apps/netconf/netconf_rpc.c | 10 +- apps/restconf/README.md | 50 +- apps/restconf/restconf_methods.c | 927 +++++++++++++----- apps/restconf/restconf_stream.c | 2 +- datastore/datastore_client.c | 9 +- datastore/text/clixon_xmldb_text.c | 97 +- doc/FAQ.md | 144 +-- example/README.md | 289 +++--- example/example_backend.c | 27 +- example/example_cli.c | 2 +- example/example_cli.cli | 2 +- example/example_netconf.c | 2 +- example/example_restconf.c | 2 +- include/clixon_custom.h | 5 - lib/clixon/clixon_json.h | 1 + lib/clixon/clixon_netconf_lib.h | 3 +- lib/clixon/clixon_options.h | 3 + lib/clixon/clixon_string.h | 2 +- lib/clixon/clixon_xml.h | 13 +- lib/clixon/clixon_xml_map.h | 6 +- lib/clixon/clixon_xml_sort.h | 8 +- lib/clixon/clixon_yang.h | 3 +- lib/src/clixon_err.c | 1 + lib/src/clixon_json.c | 151 ++- lib/src/clixon_netconf_lib.c | 86 +- lib/src/clixon_options.c | 10 +- lib/src/clixon_proto_client.c | 4 +- lib/src/clixon_string.c | 48 + lib/src/clixon_xml.c | 131 ++- lib/src/clixon_xml_db.c | 7 +- lib/src/clixon_xml_map.c | 596 +++++++---- lib/src/clixon_xml_parse.y | 35 +- lib/src/clixon_xml_sort.c | 37 +- lib/src/clixon_yang.c | 258 +---- lib/src/clixon_yang_module.c | 12 +- test/all.sh | 1 + test/test_cli.sh | 2 +- test/test_compat.sh | 116 +++ test/test_datastore.sh | 9 +- test/test_feature.sh | 14 +- test/test_identity.sh | 18 +- test/test_leafref.sh | 17 +- test/test_list.sh | 14 +- test/test_nacm.sh | 22 +- test/test_nacm_ext.sh | 18 +- test/test_nacm_protocol.sh | 14 +- test/test_netconf.sh | 49 +- test/test_order.sh | 67 +- test/test_perf.sh | 16 +- test/test_restconf.sh | 97 +- test/test_restconf2.sh | 55 +- test/test_rpc.sh | 48 +- test/test_startup.sh | 12 +- test/test_stream.sh | 27 +- test/test_type.sh | 8 +- test/test_when_must.sh | 12 +- test/test_yang.sh | 47 +- test/test_yang_load.sh | 54 +- test/test_yang_namespace.sh | 116 ++- yang/clixon-config@2018-10-21.yang | 13 +- .../ietf-netconf-notification@2008-07-01.yang | 3 +- 71 files changed, 2679 insertions(+), 1573 deletions(-) create mode 100755 test/test_compat.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index ce4188a4..ad2ebd7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,61 @@ # Clixon Changelog -## 3.9.0 (Preliminary Target: 31 December 2018) +## 3.9.0 (Preliminary Target: Mid-January 2019) ### Planned new features * [Roadmap](ROADMAP.md) (Uncommitted and unprioritized) ### Major New features +* Correct XML namespace handling + * XML multiple modules was based on non-strict semantics so that yang modules were found by iterating thorugh namespaces until a match was made. This did not adhere to proper [XML namespace handling](https://www.w3.org/TR/2009/REC-xml-names-20091208) as well as strict Netconf and Restconf namespace handling, which causes problems with overlapping names and false positives, and most importantly, with standard conformance. + * There are still the following non-strict namespace handling: + * Everything in ietf-netconf base syntax with namespace `urn:ietf:params:xml:ns:netconf:base:1.0` is default and need not be explicitly given + * edit-config xpath select statement does not support namespaces + * notifications do not support namespaces. + * Below see netconf old (but wrong) netconf RPC: + ``` + + + + + + ipv4 + + + ``` + This is the currently correct Netconf RPC: + ``` + # xmlns may be ommitted + + + + + ipv4 + + + ``` + * Another example for restconf rpc with new correct syntax. Note that while Netconf uses xmlns attribute syntax, Restconf uses module name prefix. First the request: + ``` + POST http://localhost/restconf/operations/example:example) + Content-Type: application/yang-data+json + { + "example:input":{ + "x":0 + } + } + ``` + then the reply: + ``` + HTTP/1.1 200 OK + { + "example:output": { + "x": "0", + "y": "42" + } + } + ``` + * To keep previous non-strict namespace handling (backwards compatible), set CLICON_XML_NS_STRICT to false. + * See https://github.com/clicon/clixon/issues/49 * NACM extension (RFC8341) * NACM module support (RFC8341 A1+A2) * Recovery user "_nacm_recovery" added. @@ -27,25 +77,15 @@ * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list * CLICON_YANG_MAIN_FILE Provides a filename with a single module filename. * CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded. -* Correct XML namespace handling - * XML multiple modules was based on "loose" semantics so that yang modules were found by iterating thorugh namespaces until a match was made. This did not adhere to proper [XML namespace handling](https://www.w3.org/TR/2009/REC-xml-names-20091208), and causes problems with overlapping names and false positives. Below see XML accepted (but wrong), and correct namespace declaration: - ``` - # Wrong but accepted - # Correct - - - ``` - * To keep old loose semantics set config option CLICON_XML_NS_ITERATE (true by default) - * XML to JSON translator support for mapping xmlns attribute to module name prefix. - * Default namespace is still "urn:ietf:params:xml:ns:netconf:base:1.0" - * See https://github.com/clicon/clixon/issues/49 ### API changes on existing features (you may need to change your code) -* Removed delete-config support for candidate db since it is not supported in RFC6241. +* Strict namespace setting can be a problem when upgrading existing database files, such as startup-db or persistent running-db, or any other saved XML file. + * For backward compatibility, load of startup and running set CLICON_XML_NS_STRICT to false temporarily. +* Removed `delete-config` support for candidate db since it is not supported in RFC6241. * Switched the order of `error-type` and `error-tag` in all netconf and restconf error messages to comply to RFC order. * Yang parser is stricter (see above) which may break parsing of existing yang specs. * XML namespace handling is corrected (see above) - * For backward compatibility set config option CLICON_XML_NS_ITERATE + * For backward compatibility set config option CLICON_XML_NS_LOOSE * Yang parser functions have changed signatures. Please check the source if you call these functions. * Add `/usr/local/share/clixon` to your configuration file, or corresponding CLICON_DATADIR directory for Clixon system yang files. * Change all @datamodel:tree to @datamodel in all CLI specification files @@ -53,6 +93,9 @@ * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h ### Minor changes +* Added three-valued return values for several validate functions where -1 is fatal error, 0 is validation failed and 1 is validation OK. + * This includes: `xmldb_put`, `xml_yang_validate_all`, `xml_yang_validate_add`, `xml_yang_validate_rpc`, `api_path2xml`, `api_path2xpath` +* Added new xml functions for specific types: `xml_child_nr_notype`, `xml_child_nr_notype`, `xml_child_i_type`, `xml_find_type`. * Added example_rpc RPC to example backend * Renamed xml_namespace[_set]() to xml_prefix[_set]() * Changed all make tags --> make TAGS @@ -273,7 +316,7 @@ translate { ### Known issues * Namespace name relabeling is not supported. - * Eg: if "des" is defined as prefix for an imported module, then a relabeling using xmlfns is not supported, such as: + * Eg: if "des" is defined as prefix for an imported module, then a relabeling using xmlns is not supported, such as: ``` x:des3 ``` diff --git a/DEVELOP.md b/DEVELOP.md index fb66e641..74d7f4ef 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -97,6 +97,9 @@ EOF ## New release What to think about when doing a new release. +* valgrind for memory leaks +* New clixon-config.yang revision? +Tagging: * git merge --no-ff develop * change CLIXON_VERSION in configure.ac * git tag -a +``` +In 3.9, the same statement should be, for example: +``` + +``` +Note that base netconf syntax is still not enforced but recommended: +``` + + + +``` + Yang ==== YANG and XML is the heart of Clixon. Yang modules are used as a @@ -153,6 +170,9 @@ Clixon does not support the following netconf features: - edit-config config-text - edit-config operation +Some other deviations from the RFC: +- edit-config xpath select statement does not support namespaces + Restconf ======== Clixon Restconf is a daemon based on FastCGI C-API. Instructions are available to diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index baf40269..3e1fa1ad 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -292,7 +292,7 @@ client_get_streams(clicon_handle h, * @param[in,out] xret Existing XML tree, merge x into this * @retval -1 Error (fatal) * @retval 0 OK - * @retval 1 Statedata callback failed + * @retval 1 Statedata callback failed (clicon_err called) */ static int client_statedata(clicon_handle h, @@ -309,15 +309,15 @@ client_statedata(clicon_handle h, clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; } - if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") && - (retval = client_get_streams(h, yspec, xpath, "ietf-netconf-notification", "netconf", xret)) != 0) - goto done; - if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") && - (retval = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) != 0) - goto done; - if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895") && - (retval = yang_modules_state_get(h, yspec, xret)) != 0) - goto done; + if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277")) + if ((retval = client_get_streams(h, yspec, xpath, "ietf-netconf-notification", "netconf", xret)) != 0) + goto done; + if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040")) + if ((retval = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) != 0) + goto done; + if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895")) + if ((retval = yang_modules_state_get(h, yspec, xret)) != 0) + goto done; if ((retval = clixon_plugin_statedata(h, yspec, xpath, xret)) != 0) goto done; /* Code complex to filter out anything that is outside of xpath */ @@ -339,7 +339,7 @@ client_statedata(clicon_handle h, /* reset flag */ if (xml_apply(*xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0) goto done; - retval = 0; + retval = 0; /* OK */ done: if (xvec) free(xvec); @@ -363,7 +363,6 @@ from_client_get(clicon_handle h, char *xpath = "/"; cxobj *xret = NULL; int ret; - cbuf *cbx = NULL; /* Assist cbuf */ if ((xfilter = xml_find(xe, "filter")) != NULL) if ((xpath = xml_find_value(xfilter, "select"))==NULL) @@ -379,33 +378,24 @@ from_client_get(clicon_handle h, clicon_err_reset(); if ((ret = client_statedata(h, xpath, &xret)) < 0) goto done; - if (ret == 0){ /* OK */ - cprintf(cbret, ""); - if (xret==NULL) - cprintf(cbret, ""); - else{ - if (xml_name_set(xret, "data") < 0) - goto done; - if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0) - goto done; - } - cprintf(cbret, ""); - } - else { /* 1 Error from callback */ - if ((cbx = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); + if (ret == 1){ /* Error from callback (error in xret) */ + if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0) goto done; - } - cprintf(cbx, "Internal error:%s", clicon_err_reason); - if (netconf_operation_failed(cbret, "rpc", cbuf_get(cbx))< 0) - goto done; - clicon_log(LOG_NOTICE, "%s Error in backend_statedata_call:%s", __FUNCTION__, xml_name(xe)); + goto ok; } + cprintf(cbret, ""); /* OK */ + if (xret==NULL) + cprintf(cbret, ""); + else{ + if (xml_name_set(xret, "data") < 0) + goto done; + if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0) + goto done; + } + cprintf(cbret, ""); ok: retval = 0; done: - if (cbx) - cbuf_free(cbx); if (xret) xml_free(xret); return retval; @@ -433,6 +423,7 @@ from_client_edit_config(clicon_handle h, int non_config = 0; yang_spec *yspec; cbuf *cbx = NULL; /* Assist cbuf */ + int ret; if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec9"); @@ -491,24 +482,27 @@ from_client_edit_config(clicon_handle h, /* Cant do this earlier since we dont have a yang spec to * the upper part of the tree, until we get the "config" tree. */ - if (xml_child_sort && xml_apply0(xc, CX_ELMNT, xml_sort, NULL) < 0) + if (clicon_xml_sort(h) && xml_apply0(xc, CX_ELMNT, xml_sort, NULL) < 0) goto done; - if (xmldb_put(h, target, operation, xc, cbret) < 0){ + if ((ret = xmldb_put(h, target, operation, xc, cbret)) < 0){ clicon_debug(1, "%s ERROR PUT", __FUNCTION__); if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0) goto done; goto ok; } + if (ret == 0) + goto ok; } + assert(cbuf_len(cbret) == 0); + cprintf(cbret, ""); ok: - if (!cbuf_len(cbret)) - cprintf(cbret, ""); retval = 0; done: if (cbx) cbuf_free(cbx); clicon_debug(1, "%s done cbret:%s", __FUNCTION__, cbuf_get(cbret)); return retval; + } /* from_client_edit_config */ /*! Internal message: Lock database diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 88ea9e99..7c29131a 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -165,12 +165,24 @@ validate_common(clicon_handle h, clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; } - /* 2. Parse xml trees */ + /* 2. Parse xml trees + * This is the state we are going from */ if (xmldb_get(h, "running", "/", 1, &td->td_src) < 0) goto done; + /* This is the state we are going to */ if (xmldb_get(h, candidate, "/", 1, &td->td_target) < 0) goto done; + /* Validate the target state. It is not completely clear this should be done + * here. It is being made in generic_validate below. + * But xml_diff requires some basic validation, at least check that yang-specs + * have been assigned + */ + if ((ret = xml_yang_validate_all_top(td->td_target, cbret)) < 0) + goto done; + if (ret == 0) + goto fail; + /* 3. Compute differences */ if (xml_diff(yspec, td->td_src, @@ -240,7 +252,7 @@ validate_common(clicon_handle h, * do something more drastic? * @param[in] h Clicon handle * @param[in] candidate A candidate database, not necessarily "candidate" - * @retval -1 Error - or validation failed (but cbret not set) + * @retval -1 Error - or validation failed * @retval 0 Validation failed (with cbret set) * @retval 1 Validation OK * @note Need to differentiate between error and validation fail @@ -259,7 +271,9 @@ candidate_commit(clicon_handle h, if ((td = transaction_new()) == NULL) goto done; - /* Common steps (with validate). Note this is only call that uses 3-values */ + /* Common steps (with validate). Load candidate and running and compute diffs + * Note this is only call that uses 3-values + */ if ((ret = validate_common(h, candidate, td, cbret)) < 0) goto done; if (ret == 0) @@ -270,12 +284,14 @@ candidate_commit(clicon_handle h, goto done; /* Optionally write (potentially modified) tree back to candidate */ - if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")) - if (xmldb_put(h, candidate, OP_REPLACE, td->td_target, NULL) < 0) + if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){ + if ((ret = xmldb_put(h, candidate, OP_REPLACE, td->td_target, NULL)) < 0) goto done; + if (ret == 0) + goto fail; + } /* 8. Success: Copy candidate to running */ - if (xmldb_copy(h, candidate, "running") < 0) goto done; @@ -423,9 +439,11 @@ from_client_validate(clicon_handle h, goto ok; } /* Optionally write (potentially modified) tree back to candidate */ - if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")) - if (xmldb_put(h, "candidate", OP_REPLACE, td->td_target, NULL) < 0) - goto done; + if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){ + if ((ret = xmldb_put(h, "candidate", OP_REPLACE, td->td_target, cbret)) < 0) + goto done; + goto ok; + } cprintf(cbret, ""); ok: retval = 0; diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 8e433d85..ba297852 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -176,22 +176,24 @@ db_reset(clicon_handle h, } /*! Merge db1 into db2 without commit + * @retval -1 Error + * @retval 0 Validation failed (with cbret set) + * @retval 1 Validation OK */ static int db_merge(clicon_handle h, const char *db1, - const char *db2) + const char *db2, + cbuf *cbret) { - int retval = -1; - cxobj *xt = NULL; - + int retval = -1; + cxobj *xt = NULL; + /* Get data as xml from db1 */ if (xmldb_get(h, (char*)db1, NULL, 1, &xt) < 0) goto done; /* Merge xml into db2. Without commit */ - if (xmldb_put(h, (char*)db2, OP_MERGE, xt, NULL) < 0) - goto done; - retval = 0; + retval = xmldb_put(h, (char*)db2, OP_MERGE, xt, cbret); done: if (xt) xml_free(xt); @@ -288,18 +290,22 @@ nacm_load_external(clicon_handle h) } /*! Merge xml in filename into database + * @retval -1 Error + * @retval 0 Validation failed (with cbret set) + * @retval 1 Validation OK */ static int load_extraxml(clicon_handle h, char *filename, - const char *db) + const char *db, + cbuf *cbret) { int retval = -1; cxobj *xt = NULL; int fd = -1; - + if (filename == NULL) - return 0; + return 1; if ((fd = open(filename, O_RDONLY)) < 0){ clicon_err(OE_UNIX, errno, "open(%s)", filename); goto done; @@ -310,9 +316,7 @@ load_extraxml(clicon_handle h, if (xml_rootchild(xt, 0, &xt) < 0) goto done; /* Merge user reset state */ - if (xmldb_put(h, (char*)db, OP_MERGE, xt, NULL) < 0) - goto done; - retval = 0; + retval = xmldb_put(h, (char*)db, OP_MERGE, xt, cbret); done: if (fd != -1) close(fd); @@ -385,9 +389,13 @@ static int startup_mode_running(clicon_handle h, char *extraxml_file) { - int retval = -1; - cbuf *cbret = NULL; + int retval = -1; + cbuf *cbret = NULL; + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } /* Stash original running to candidate for later commit */ if (xmldb_copy(h, "running", "candidate") < 0) goto done; @@ -400,41 +408,46 @@ startup_mode_running(clicon_handle h, /* Application may define extra xml in its reset function*/ if (clixon_plugin_reset(h, "tmp") < 0) goto done; + /* XXX Kludge to low-level functions to search for xml in all yang modules */ + _CLICON_XML_NS_STRICT = 0; /* Get application extra xml from file */ - if (load_extraxml(h, extraxml_file, "tmp") < 0) - goto done; + if (load_extraxml(h, extraxml_file, "tmp", cbret) < 1) + goto fail; /* Clear running db */ if (db_reset(h, "running") < 0) goto done; - if ((cbret = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } /* Commit original running. Assume -1 is validate fail */ - if (candidate_commit(h, "candidate", cbret) < 1){ - /* (1) We cannot differentiate between fatal errors and validation - * failures - * (2) If fatal error, we should exit - * (3) If validation fails we cannot continue. How could we? - * (4) Need to restore the running db since we destroyed it above - */ - clicon_log(LOG_NOTICE, "%s: Commit of saved running failed, exiting: %s.", - __FUNCTION__, cbuf_get(cbret)); - /* Reinstate original */ - if (xmldb_copy(h, "candidate", "running") < 0) - goto done; - goto done; - } + if (candidate_commit(h, "candidate", cbret) < 1) + goto fail; /* Merge user reset state and extra xml file (no commit) */ - if (db_merge(h, "tmp", "running") < 0) - goto done; + if (db_merge(h, "tmp", "running", cbret) < 1) + goto fail; retval = 0; done: + /* XXX Kludge to low-level functions to search for xml in all yang modules */ + _CLICON_XML_NS_STRICT = clicon_option_bool(h, "CLICON_XML_NS_STRICT"); if (cbret) cbuf_free(cbret); if (xmldb_delete(h, "tmp") < 0) goto done; return retval; + fail: + /* (1) We cannot differentiate between fatal errors and validation + * failures + * (2) If fatal error, we should exit + * (3) If validation fails we cannot continue. How could we? + * (4) Need to restore the running db since we destroyed it above + */ + if (strlen(cbuf_get(cbret))) + clicon_log(LOG_NOTICE, "%s: Commit of running failed, exiting: %s.", + __FUNCTION__, cbuf_get(cbret)); + else + clicon_log(LOG_NOTICE, "%s: Commit of running failed, exiting: %s.", + __FUNCTION__, clicon_err_reason); + /* Reinstate original */ + if (xmldb_copy(h, "candidate", "running") < 0) + goto done; + goto done; } /*! Clixon startup startup mode: Commit startup configuration into running state @@ -460,11 +473,16 @@ startup -------------------------+--| */ static int startup_mode_startup(clicon_handle h, - char *extraxml_file) + char *extraxml_file) { int retval = -1; cbuf *cbret = NULL; + /* Create return buffer for netconf xml errors */ + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } /* Stash original running to backup */ if (xmldb_copy(h, "running", "backup") < 0) goto done; @@ -481,34 +499,25 @@ startup_mode_startup(clicon_handle h, /* Application may define extra xml in its reset function*/ if (clixon_plugin_reset(h, "tmp") < 0) goto done; + /* XXX Kludge to low-level functions to search for xml in all yang modules */ + _CLICON_XML_NS_STRICT = 0; /* Get application extra xml from file */ - if (load_extraxml(h, extraxml_file, "tmp") < 0) - goto done; + if (load_extraxml(h, extraxml_file, "tmp", cbret) < 1) + goto fail; /* Clear running db */ if (db_reset(h, "running") < 0) goto done; - /* Create return buffer (not used) */ - if ((cbret = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } + /* Commit startup */ - if (candidate_commit(h, "startup", cbret) < 1){ /* diff */ - /* We cannot differentiate between fatal errors and validation - * failures - * In both cases we copy back the original running and quit - */ - clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting: %s.", - __FUNCTION__, cbuf_get(cbret)); - if (xmldb_copy(h, "backup", "running") < 0) - goto done; - goto done; - } + if (candidate_commit(h, "startup", cbret) < 1) /* diff */ + goto fail; /* Merge user reset state and extra xml file (no commit) */ - if (db_merge(h, "tmp", "running") < 0) - goto done; + if (db_merge(h, "tmp", "running", cbret) < 1) + goto fail; retval = 0; done: + /* XXX Kludge to low-level functions to search for xml in all yang modules */ + _CLICON_XML_NS_STRICT = clicon_option_bool(h, "CLICON_XML_NS_STRICT"); if (cbret) cbuf_free(cbret); if (xmldb_delete(h, "backup") < 0) @@ -516,6 +525,20 @@ startup_mode_startup(clicon_handle h, if (xmldb_delete(h, "tmp") < 0) goto done; return retval; + fail: + /* We cannot differentiate between fatal errors and validation + * failures + * In both cases we copy back the original running and quit + */ + if (strlen(cbuf_get(cbret))) + clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting: %s.", + __FUNCTION__, cbuf_get(cbret)); + else + clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting: %s.", + __FUNCTION__, clicon_err_reason); + if (xmldb_copy(h, "backup", "running") < 0) + goto done; + goto done; } int @@ -540,7 +563,6 @@ main(int argc, int sockfamily; char *xmldb_plugin; int xml_cache; - int xml_pretty; char *xml_format; char *nacm_mode; int logdst = CLICON_LOG_SYSLOG|CLICON_LOG_STDERR; @@ -808,9 +830,10 @@ main(int argc, if ((xml_format = clicon_option_str(h, "CLICON_XMLDB_FORMAT")) >= 0) if (xmldb_setopt(h, "format", (void*)xml_format) < 0) goto done; - if ((xml_pretty = clicon_option_bool(h, "CLICON_XMLDB_PRETTY")) >= 0) - if (xmldb_setopt(h, "pretty", (void*)(intptr_t)xml_pretty) < 0) - goto done; + if (xmldb_setopt(h, "pretty", (void*)(intptr_t)clicon_option_bool(h, "CLICON_XMLDB_PRETTY")) < 0) + goto done; + if (xmldb_setopt(h, "sort", (void*)(intptr_t)clicon_option_bool(h, "CLICON_XML_SORT")) < 0) + goto done; /* Startup mode needs to be defined, */ startup_mode = clicon_startup_mode(h); if (startup_mode == -1){ diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index e8ce2972..996a27cb 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -117,7 +117,7 @@ clixon_plugin_reset(clicon_handle h, * @param[in,out] xtop State XML tree is merged with existing tree. * @retval -1 Error * @retval 0 OK - * @retval 1 Statedata callback failed + * @retval 1 Statedata callback failed (xret set with netconf-error) * @note xtop can be replaced */ int diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 7e6f1307..f6796f58 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -236,8 +236,8 @@ cli_dbxml(clicon_handle h, if ((xtop = xml_new("config", NULL, NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) - goto done; + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 1) + goto done; if ((xa = xml_new("operation", xbot, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); @@ -293,7 +293,7 @@ cli_set(clicon_handle h, cvec *cvv, cvec *argv) { - int retval = 1; + int retval = -1; if (cli_dbxml(h, cvv, argv, OP_REPLACE) < 0) goto done; @@ -501,53 +501,54 @@ cli_start_shell(clicon_handle h, cvec *vars, cvec *argv) { - char *cmd; + char *cmd; struct passwd *pw; - int retval; - char bcmd[128]; - cg_var *cv1 = cvec_i(vars, 1); + int retval = -1; + char bcmd[128]; + cg_var *cv1 = cvec_i(vars, 1); cmd = (cvec_len(vars)>1 ? cv_string_get(cv1) : NULL); if ((pw = getpwuid(getuid())) == NULL){ fprintf(stderr, "%s: getpwuid: %s\n", __FUNCTION__, strerror(errno)); - return -1; + goto done; } if (chdir(pw->pw_dir) < 0){ fprintf(stderr, "%s: chdir(%s): %s\n", __FUNCTION__, pw->pw_dir, strerror(errno)); endpwent(); - return -1; + goto done; } endpwent(); cli_signal_flush(h); cli_signal_unblock(h); if (cmd){ snprintf(bcmd, 128, "bash -l -c \"%s\"", cmd); - if ((retval = system(bcmd)) < 0){ + if (system(bcmd) < 0){ cli_signal_block(h); fprintf(stderr, "%s: system(bash -c): %s\n", __FUNCTION__, strerror(errno)); - return -1; + goto done; } } else - if ((retval = system("bash -l")) < 0){ + if (system("bash -l") < 0){ cli_signal_block(h); fprintf(stderr, "%s: system(bash): %s\n", __FUNCTION__, strerror(errno)); - return -1; + goto done; } cli_signal_block(h); #if 0 /* Allow errcodes from bash */ if (retval != 0){ fprintf(stderr, "%s: system(%s) code=%d\n", __FUNCTION__, cmd, retval); - return -1; + goto done; } #endif - - return 0; + retval = 0; + done: + return retval; } /*! Generic quit callback diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 2e56f329..ec39ac4e 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -171,7 +171,7 @@ expand_dbvar(void *h, /* This is primarily to get "y", * xpath2xml would have worked!! */ - if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 1) goto done; if (y==NULL) goto ok; @@ -443,6 +443,7 @@ cli_show_config(clicon_handle h, cxobj *xc; cxobj *xerr; enum genmodel_type gt; + yang_spec *yspec; if (cvec_len(argv) != 3 && cvec_len(argv) != 4){ clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: ,,[,]", cvec_len(argv)); @@ -496,6 +497,13 @@ cli_show_config(clicon_handle h, clicon_rpc_generate_error("Get configuration", xerr); goto done; } + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_FATAL, 0, "No DB_SPEC"); + goto done; + } + /* Some formats (eg cli) require yang */ + if (xml_apply(xt, CX_ELMNT, xml_spec_populate, yspec) < 0) + goto done; /* Print configuration according to format */ switch (format){ case FORMAT_XML: @@ -516,7 +524,7 @@ cli_show_config(clicon_handle h, if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR) goto done; xc = NULL; /* Dont print xt itself */ - while ((xc = xml_child_each(xt, xc, -1)) != NULL) + while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) xml2cli(stdout, xc, NULL, gt); /* cli syntax */ break; case FORMAT_NETCONF: diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index dc57126e..4cdef1a0 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -118,6 +118,8 @@ process_incoming_packet(clicon_handle h, free(str0); if ((xrpc=xpath_first(xreq, "//rpc")) != NULL){ isrpc++; + if (xml_spec_populate_rpc(h, xrpc, yspec) < 0) + goto done; if ((ret = xml_yang_validate_rpc(xrpc, cbret)) < 0) goto done; if (ret == 0){ diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 4d9b354a..7945110e 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -905,13 +905,9 @@ netconf_application_rpc(clicon_handle h, goto ok; } yrpc = yang_find((yang_node*)ymod, Y_RPC, xml_name(xn)); - if ((yrpc==NULL) && _CLICON_XML_NS_ITERATE){ - int i; - for (i=0; iyp_len; i++){ - ymod = yspec->yp_stmt[i]; - if ((yrpc = yang_find((yang_node*)ymod, Y_RPC, xml_name(xn))) != NULL) - break; - } + if ((yrpc==NULL) && !_CLICON_XML_NS_STRICT){ + if (xml_yang_find_non_strict(xn, yspec, &yrpc) < 0) /* Y_RPC */ + goto done; } /* Check if found */ if (yrpc != NULL){ diff --git a/apps/restconf/README.md b/apps/restconf/README.md index 972ab99f..2295e3fa 100644 --- a/apps/restconf/README.md +++ b/apps/restconf/README.md @@ -1,11 +1,11 @@ # Clixon Restconf - * [Installation](#Installation) - * [Streams](Streams) - * [Nchan Streams](Nchan) - * [Debugging](Debugging) + * [Installation](#installation) + * [Streams](#streams) + * [Nchan Streams](#nchan-streams) + * [Debugging](#debugging) -### 1. Installation +## 1. Installation The examples are based on Nginx. Other reverse proxies should work but are not verified. @@ -44,39 +44,39 @@ sudo systemctl start start.service Start clixon restconf daemon ``` -olof@vandal> sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data +> sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data ``` Make restconf calls with curl ``` -olof@vandal> curl -G http://127.0.0.1/restconf/data/interfaces +> curl -G http://127.0.0.1/restconf/data/ietf-interfaces:interfaces [ { - "interfaces": { + "ietf-interfaces:interfaces": { "interface":[ { - "name": "eth0", - "type": "eth", - "enabled": "true", "name": "eth9", - "type": "eth", - "enabled": "true" + "type": "ex:eth", + "enabled": true, } ] } } ] -olof@vandal> curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=eth9/type -[ - { - "type": "eth" - } -] - -curl -sX POST -d '{"interfaces":{"interface":{"name":"eth1","type":"eth","enabled":"true"}}}' http://localhost/restconf/data +``` +Get the type of a specific interface: +``` +> curl -G http://127.0.0.1/restconf/data/interfaces/interface=eth9/type +{ + "ietf-interfaces:type": "eth" +} +``` +Example of writing a new interfaces specification: +``` +curl -sX PUT http://localhost/restconf/data -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth1","type":"ex:eth","enabled":true}}}' ``` -### 2. Streams +## 2. Streams Clixon have two experimental restconf event stream implementations following RFC8040 Section 6 using SSE. One native and one using Nginx @@ -112,7 +112,7 @@ Add the following to extend the nginx configuration file with the following stat AN example of a stream access is as follows: ``` -vandal> curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE +> curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE data: 2018-11-04T14:47:11.373124faultEthernet0major data: 2018-11-04T14:47:16.375265faultEthernet0major @@ -125,7 +125,7 @@ You can also specify start and stop time. Start-time enables replay of existing See (stream tests)[../test/test_streams.sh] for more examples. -### 3. Nchan +## 3. Nchan As an alternative streams implementation, Nginx/Nchan can be used. Nginx uses pub/sub channels and can be configured in a variety of @@ -180,7 +180,7 @@ curl -H "Accept: text/event-stream" -H "Last-Event-ID: 1539961709:0" -s -X GET h See (https://nchan.io/#eventsource) on more info on how to access an SSE sub endpoint. -### 4. Debugging +## 4. Debugging Start the restconf fastcgi program with debug flag: ``` diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index a11d55c1..b2910a1b 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -93,6 +93,7 @@ Mapping netconf error-tag -> status code | malformed-message | 400 | +-------------------------+-------------+ + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 */ #ifdef HAVE_CONFIG_H @@ -195,20 +196,26 @@ api_data_get2(clicon_handle h, size_t xlen; int i; cxobj *x; - + int ret; + clicon_debug(1, "%s", __FUNCTION__); yspec = clicon_dbspec_yang(h); if ((cbpath = cbuf_new()) == NULL) goto done; cprintf(cbpath, "/"); - clicon_debug(1, "%s pi:%d", __FUNCTION__, pi); /* We know "data" is element pi-1 */ - if (api_path2xpath_cvv(yspec, pcvec, pi, cbpath) < 0){ + if ((ret = api_path2xpath(yspec, pcvec, pi, cbpath)) < 0) + goto done; + if (ret == 0){ if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + clicon_err_reset(); + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } path = cbuf_get(cbpath); @@ -216,24 +223,29 @@ api_data_get2(clicon_handle h, if (clicon_rpc_get(h, path, &xret) < 0){ if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } + if (xml_apply(xret, CX_ELMNT, xml_spec_populate, yspec) < 0) + goto done; /* We get return via netconf which is complete tree from root * We need to cut that tree to only the object. */ -#if 1 /* DEBUG */ - { +#if 0 /* DEBUG */ + if (debug){ cbuf *cb = cbuf_new(); clicon_xml2cbuf(cb, xret, 0, 0); clicon_debug(1, "%s xret:%s", __FUNCTION__, cbuf_get(cb)); cbuf_free(cb); } #endif - /* Check if error return XXX this needs more work */ - if ((xe = xpath_first(xret, "/rpc-error")) != NULL){ + /* Check if error return */ + if ((xe = xpath_first(xret, "//rpc-error")) != NULL){ if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; @@ -241,11 +253,12 @@ api_data_get2(clicon_handle h, /* Normal return, no error */ if ((cbx = cbuf_new()) == NULL) goto done; - FCGX_SetExitStatus(200, r->out); /* OK */ - FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); - FCGX_FPrintF(r->out, "\r\n"); - if (head) + if (head){ + FCGX_SetExitStatus(200, r->out); /* OK */ + FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); + FCGX_FPrintF(r->out, "\r\n"); goto ok; + } if (path==NULL || strcmp(path,"/")==0){ /* Special case: data root */ if (use_xml){ if (clicon_xml2cbuf(cbx, xret, 0, pretty) < 0) /* Dont print top object? */ @@ -261,16 +274,32 @@ api_data_get2(clicon_handle h, goto done; if (use_xml){ for (i=0; i0 + * Out: {"example:x": {"0"}} + */ if (xml2json_cbuf_vec(cbx, xvec, xlen, pretty) < 0) goto done; + } } - // clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx)); + clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx)); + FCGX_SetExitStatus(200, r->out); /* OK */ + FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); + FCGX_FPrintF(r->out, "\r\n"); FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); FCGX_FPrintF(r->out, "\r\n\r\n"); ok: @@ -414,7 +443,8 @@ api_data_post(clicon_handle h, cxobj *xerr = NULL; /* malloced must be freed */ cxobj *xe; /* dont free */ char *username; - + int ret; + clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", __FUNCTION__, api_path, data); @@ -429,25 +459,45 @@ api_data_post(clicon_handle h, goto done; /* Translate api_path to xtop/xbot */ xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) - goto done; + if (api_path){ + if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y)) < 0) + goto done; + if (ret == 0){ /* validation failed */ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + clicon_err_reset(); + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + } /* Parse input data as json or xml into xml */ if (parse_xml){ if (xml_parse_string(data, NULL, &xdata) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } } else if (json_parse_str(data, &xdata) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } /* 4.4.1: The message-body MUST contain exactly one instance of the @@ -456,9 +506,12 @@ api_data_post(clicon_handle h, if (xml_child_nr(xdata) != 1){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } x = xml_child_i(xdata,0); @@ -471,6 +524,19 @@ api_data_post(clicon_handle h, /* Replace xbot with x, ie bottom of api-path with data */ if (xml_addsub(xbot, x) < 0) goto done; + if (!parse_xml){ /* If JSON, translate namespace from module:name to xmlns=uri */ + if (json2xml_ns(yspec, x, &xerr) < 0) + goto done; + if (xerr){ + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + } /* Create text buffer for transfer to backend */ if ((cbx = cbuf_new()) == NULL) goto done; @@ -509,7 +575,7 @@ api_data_post(clicon_handle h, /* log errors from discard, but ignore */ if ((xpath_first(xretdis, "//rpc-error")) != NULL) clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) /* Use original xe */ goto done; goto ok; } @@ -644,6 +710,8 @@ api_data_put(clicon_handle h, cxobj *xerr = NULL; /* malloced must be freed */ cxobj *xe; char *username; + int ret; + char *namespace0; clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", __FUNCTION__, api_path0, data); @@ -659,46 +727,91 @@ api_data_put(clicon_handle h, goto done; /* Translate api_path to xtop/xbot */ xbot = xtop; - - if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) - goto done; + if (api_path){ + if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y)) < 0) + goto done; + if (ret == 0){ /* validation failed */ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + clicon_err_reset(); + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + } /* Parse input data as json or xml into xml */ if (parse_xml){ if (xml_parse_string(data, NULL, &xdata) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } } - else if (json_parse_str(data, &xdata) < 0){ - if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) - goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + else{ + if (json_parse_str(data, &xdata) < 0){ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; - goto ok; - } + goto ok; + } + } /* The message-body MUST contain exactly one instance of the * expected data resource. */ if (xml_child_nr(xdata) != 1){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } x = xml_child_i(xdata,0); + if (!parse_xml){ /* If JSON, translate namespace from module:name to xmlns=uri */ + if (json2xml_ns(yspec, x, &xerr) < 0) + goto done; + if (xerr){ + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + } /* Add operation (create/replace) as attribute */ if ((xa = xml_new("operation", x, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); if (xml_value_set(xa, xml_operation2str(op)) < 0) goto done; +#if 0 + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xdata, 0, 0) < 0) + goto done; + clicon_debug(1, "%s DATA:%s", __FUNCTION__, cbuf_get(ccc)); + } +#endif /* Replace xparent with x, ie bottom of api-path with data */ if (api_path==NULL && strcmp(xml_name(x),"data")==0){ if (xml_addsub(NULL, x) < 0) @@ -713,9 +826,12 @@ api_data_put(clicon_handle h, if (strcmp(xml_name(x), xml_name(xbot))){ if (netconf_operation_failed_xml(&xerr, "protocol", "Not same symbol in api-path as data") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } /* If list or leaf-list, api-path keys must match data keys */ @@ -723,9 +839,12 @@ api_data_put(clicon_handle h, if (match_list_keys((yang_stmt*)y, x, xbot) < 0){ if (netconf_operation_failed_xml(&xerr, "protocol", "api-path keys do not match data keys") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } } @@ -733,8 +852,15 @@ api_data_put(clicon_handle h, xml_purge(xbot); if (xml_addsub(xparent, x) < 0) goto done; + /* If we already have that default namespace, remove it in child */ + if ((xa = xml_find_type(x, NULL, "xmlns", CX_ATTR)) != NULL){ + if (xml2ns(xparent, NULL, &namespace0) < 0) + goto done; + /* Set xmlns="" default namespace attribute (if diff from default) */ + if (strcmp(namespace0, xml_value(xa))==0) + xml_purge(xa); + } } - /* Create text buffer for transfer to backend */ if ((cbx = cbuf_new()) == NULL) goto done; @@ -859,6 +985,8 @@ api_data_delete(clicon_handle h, cxobj *xretdis = NULL; /* return from discard */ cxobj *xerr = NULL; char *username; + int ret; + cxobj *xe; /* xml error, no free */ clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path); if ((yspec = clicon_dbspec_yang(h)) == NULL){ @@ -871,8 +999,22 @@ api_data_delete(clicon_handle h, if ((xtop = xml_new("config", NULL, NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) - goto done; + if (api_path){ + if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y)) < 0) + goto done; + if (ret == 0){ /* validation failed */ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + clicon_err_reset(); + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + } if ((xa = xml_new("operation", xbot, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); @@ -891,8 +1033,8 @@ api_data_delete(clicon_handle h, cprintf(cbx, ""); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + if ((xe = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; } @@ -905,7 +1047,7 @@ api_data_delete(clicon_handle h, cprintf(cbx, ""); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; - if ((xerr = xpath_first(xretcom, "//rpc-error")) != NULL){ + if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){ cbuf_reset(cbx); cprintf(cbx, "", NACM_RECOVERY_USER); cprintf(cbx, ""); @@ -914,7 +1056,7 @@ api_data_delete(clicon_handle h, /* log errors from discard, but ignore */ if ((xpath_first(xretdis, "//rpc-error")) != NULL) clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; } @@ -981,36 +1123,41 @@ api_operations_get(clicon_handle h, char *namespace; cbuf *cbx = NULL; cxobj *xt = NULL; + int i; clicon_debug(1, "%s", __FUNCTION__); yspec = clicon_dbspec_yang(h); if ((cbx = cbuf_new()) == NULL) goto done; - cprintf(cbx, ""); + if (use_xml) + cprintf(cbx, ""); + else + cprintf(cbx, "{\"operations\": "); ymod = NULL; while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) { namespace = yang_find_mynamespace(ymod); - yc = NULL; + yc = NULL; i=0; while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) { if (yc->ys_keyword != Y_RPC) continue; - cprintf(cbx, "<%s xmlns=\"%s\"/>", yc->ys_argument, namespace); + if (use_xml) + cprintf(cbx, "<%s xmlns=\"%s\"/>", yc->ys_argument, namespace); + else{ + if (i==0) + cprintf(cbx, "{"); + if (i) + cprintf(cbx, ","); + cprintf(cbx, "\"%s:%s\": null", ymod->ys_argument, yc->ys_argument); + } + i++; } + if (!use_xml && i) + cprintf(cbx, "}"); } - cprintf(cbx, ""); - if (xml_parse_string(cbuf_get(cbx), yspec, &xt) < 0) - goto done; - if (xml_rootchild(xt, 0, &xt) < 0) - goto done; - cbuf_reset(cbx); /* reuse same cbuf */ - if (use_xml){ - if (clicon_xml2cbuf(cbx, xt, 0, pretty) < 0) /* Dont print top object? */ - goto done; - } - else{ - if (xml2json_cbuf(cbx, xt, pretty) < 0) - goto done; - } + if (use_xml) + cprintf(cbx, ""); + else + cprintf(cbx, "}"); FCGX_SetExitStatus(200, r->out); /* OK */ FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); FCGX_FPrintF(r->out, "\r\n"); @@ -1027,6 +1174,298 @@ api_operations_get(clicon_handle h, return retval; } +/*! Handle input data to api_operations_post + * @param[in] h CLIXON handle + * @param[in] r Fastcgi request handle + * @param[in] data Stream input data + * @param[in] yspec Yang top-level specification + * @param[in] yrpc Yang rpc spec + * @param[in] xrpc XML pointer to rpc method + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML for output data + * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data + * @retval 1 OK + * @retval 0 Fail, Error message sent + * @retval -1 Fatal error, clicon_err called + * + * RFC8040 3.6.1 + * If the "rpc" or "action" statement has an "input" section, then + * instances of these input parameters are encoded in the module + * namespace where the "rpc" or "action" statement is defined, in an XML + * element or JSON object named "input", which is in the module + * namespace where the "rpc" or "action" statement is defined. + * (Any other input is assumed as error.) + */ +static int +api_operations_post_input(clicon_handle h, + FCGX_Request *r, + char *data, + yang_spec *yspec, + yang_stmt *yrpc, + cxobj *xrpc, + int pretty, + int use_xml, + int parse_xml) +{ + int retval = -1; + cxobj *xdata = NULL; + cxobj *xerr = NULL; /* malloced must be freed */ + cxobj *xe; + cxobj *xinput; + cxobj *x; + cbuf *cbret = NULL; + + clicon_debug(1, "%s %s", __FUNCTION__, data); + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "cbuf_new"); + goto done; + } + /* Parse input data as json or xml into xml */ + if (parse_xml){ + if (xml_parse_string(data, yspec, &xdata) < 0){ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + } + else { /* JSON */ + if (json_parse_str(data, &xdata) < 0){ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + /* Special case for JSON: It looks like: + * Need to translate to + */ + if (json2xml_ns(yspec, xdata, &xerr) < 0) + goto done; + if (xerr){ + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + } + xml_name_set(xdata, "data"); + /* Here xdata is: + * ... + * Validate that exactly only tag + */ +#if 0 + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xdata, 0, 0) < 0) + goto done; + clicon_debug(1, "%s DATA:%s", __FUNCTION__, cbuf_get(ccc)); + } +#endif + if ((xinput = xml_child_i_type(xdata, 0, CX_ELMNT)) == NULL || + strcmp(xml_name(xinput),"input") != 0 || + xml_child_nr_type(xdata, CX_ELMNT) != 1){ + + if (xml_child_nr_type(xdata, CX_ELMNT) == 0){ + if (netconf_malformed_message_xml(&xerr, "restconf RPC does not have input statement") < 0) + goto done; + } + else + if (netconf_malformed_message_xml(&xerr, "restconf RPC has malformed input statement (multiple or not called input)") < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + // clicon_debug(1, "%s input validation passed", __FUNCTION__); + /* Add all input under path */ + x = NULL; + while ((x = xml_child_i_type(xinput, 0, CX_ELMNT)) != NULL) + if (xml_addsub(xrpc, x) < 0) + goto done; + /* Here xrpc is: 42 + */ + // ok: + retval = 1; + done: + clicon_debug(1, "%s retval: %d", __FUNCTION__, retval); + if (cbret) + cbuf_free(cbret); + if (xerr) + xml_free(xerr); + if (xdata) + xml_free(xdata); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Handle output data to api_operations_post + * @param[in] h CLIXON handle + * @param[in] r Fastcgi request handle + * @param[in] xret XML reply messages from backend/handler + * @param[in] yspec Yang top-level specification + * @param[in] youtput Yang rpc output specification + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML for output data + * @param[out] xoutputp Restconf JSON/XML output + * @retval 1 OK + * @retval 0 Fail, Error message sent + * @retval -1 Fatal error, clicon_err called + * xret should like: 0 + */ +static int +api_operations_post_output(clicon_handle h, + FCGX_Request *r, + cxobj *xret, + yang_spec *yspec, + yang_stmt *youtput, + char *namespace, + int pretty, + int use_xml, + cxobj **xoutputp) + +{ + int retval = -1; + cxobj *xoutput = NULL; + cxobj *xerr = NULL; /* assumed malloced, will be freed */ + cxobj *xe; /* just pointer */ + cxobj *xa; /* xml attribute (xmlns) */ + cxobj *x; + cxobj *xok; + cbuf *cbret = NULL; + int ret; + + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "cbuf_new"); + goto done; + } + /* Validate that exactly only tag */ + if ((xoutput = xml_child_i_type(xret, 0, CX_ELMNT)) == NULL || + strcmp(xml_name(xoutput),"rpc-reply") != 0 || + xml_child_nr_type(xret, CX_ELMNT) != 1){ + if (netconf_malformed_message_xml(&xerr, "restconf RPC does not have single input") < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + /* xoutput should now look: 0 */ + /* 9. Translate to restconf RPC data */ + xml_name_set(xoutput, "output"); + /* xoutput should now look: 0 */ +#if 0 + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xoutput, 0, 0) < 0) + goto done; + clicon_debug(1, "%s XOUTPUT:%s", __FUNCTION__, cbuf_get(ccc)); + } +#endif + /* Validate output (in case handlers are wrong) */ + if (youtput==NULL){ + /* Special case, no yang output + * RFC 7950 7.14.4 + * If the RPC operation invocation succeeded and no output parameters + * are returned, the contains a single element + * RFC 8040 3.6.2 + * If the "rpc" statement has no "output" section, the response message + * MUST NOT include a message-body and MUST send a "204 No Content" + * status-line instead. + */ + if ((xok = xml_child_i_type(xoutput, 0, CX_ELMNT)) == NULL || + strcmp(xml_name(xok),"ok") != 0 || + xml_child_nr_type(xoutput, CX_ELMNT) != 1){ + /* Internal error - invalid output from rpc handler */ + if (xok){ + if (netconf_operation_failed_xml(&xerr, "application", + "Internal error: Empty RPC reply is not ok") < 0) + goto done; + } + else + if (netconf_operation_failed_xml(&xerr, "application", + "Internal error: Empty RPC reply should have OK") < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + FCGX_SetExitStatus(204, r->out); /* OK */ + FCGX_FPrintF(r->out, "Status: 204 No Content\r\n"); + FCGX_FPrintF(r->out, "\r\n"); + goto fail; + } + else{ + xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ + if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0) + goto done; + if ((ret = xml_yang_validate_all(xoutput, cbret)) < 0) + goto done; + if (ret == 1 && + (ret = xml_yang_validate_add(xoutput, cbret)) < 0) + goto done; + if (ret == 0){ /* validation failed */ + if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-reply/rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + + /* Clear namespace of parameters */ + x = NULL; + while ((x = xml_child_each(xoutput, x, CX_ELMNT)) != NULL) { + if ((xa = xml_find_type(x, NULL, "xmlns", CX_ATTR)) != NULL) + if (xml_purge(xa) < 0) + goto done; + } + } + /* Set namespace on output */ + if (xmlns_set(xoutput, NULL, namespace) < 0) + goto done; + *xoutputp = xoutput; + retval = 1; + done: + clicon_debug(1, "%s retval: %d", __FUNCTION__, retval); + if (cbret) + cbuf_free(cbret); + if (xerr) + xml_free(xerr); + return retval; + fail: + retval = 0; + goto done; +} + /*! REST operation POST method * @param[in] h CLIXON handle * @param[in] r Fastcgi request handle @@ -1040,7 +1479,24 @@ api_operations_get(clicon_handle h, * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data * See RFC 8040 Sec 3.6 / 4.4.2 * @note We map post to edit-config create. - POST {+restconf}/operations/ + * POST {+restconf}/operations/ + * 1. Initialize + * 2. Get rpc module and name from uri (oppath) and find yang spec + * 3. Build xml tree with user and rpc: + * 4. Parse input data (arguments): + * JSON: {"example:input":{"x":0}} + * XML: 0 + * 5. Translate input args to Netconf RPC, add to xml tree: + * 42 + * 6. Validate outgoing RPC and fill in default values + * 4299 + * 7. Send to RPC handler, either local or backend + * 8. Receive reply from local/backend handler as Netconf RPC + * 0 + * 9. Translate to restconf RPC data: + * JSON: {"example:output":{"x":0}} + * XML: 0 + * 10. Validate and send reply to originator */ int api_operations_post(clicon_handle h, @@ -1057,20 +1513,15 @@ api_operations_post(clicon_handle h, int retval = -1; int i; char *oppath = path; - yang_stmt *yrpc = NULL; yang_spec *yspec; - yang_stmt *yinput; - yang_stmt *youtput; - cxobj *xdata = NULL; + yang_stmt *youtput = NULL; + yang_stmt *yrpc = NULL; cxobj *xret = NULL; cxobj *xerr = NULL; /* malloced must be freed */ - cbuf *cbx = NULL; cxobj *xtop = NULL; /* xpath root */ cxobj *xbot = NULL; yang_node *y = NULL; - cxobj *xinput; - cxobj *xoutput; - cxobj *x; + cxobj *xoutput = NULL; cxobj *xa; cxobj *xe; char *username; @@ -1079,8 +1530,10 @@ api_operations_post(clicon_handle h, char *prefix = NULL; char *id = NULL; yang_stmt *ys = NULL; + char *namespace = NULL; clicon_debug(1, "%s json:\"%s\" path:\"%s\"", __FUNCTION__, data, path); + /* 1. Initialize */ if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; @@ -1094,216 +1547,188 @@ api_operations_post(clicon_handle h, if (oppath == NULL || strcmp(oppath,"/")==0){ if (netconf_operation_failed_xml(&xerr, "protocol", "Operation name expected") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } - clicon_debug(1, "%s oppath: %s", __FUNCTION__, oppath); - - /* Find yang rpc statement, return yang rpc statement if found + /* 2. Get rpc module and name from uri (oppath) and find yang spec * POST {+restconf}/operations/ * * The field identifies the module name and rpc identifier * string for the desired operation. */ - if (yang_nodeid_split(oppath+1, &prefix, &id) < 0) /* +1 skip / */ + if (nodeid_split(oppath+1, &prefix, &id) < 0) /* +1 skip / */ goto done; if ((ys = yang_find((yang_node*)yspec, Y_MODULE, prefix)) == NULL){ if (netconf_operation_failed_xml(&xerr, "protocol", "yang module not found") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } if ((yrpc = yang_find((yang_node*)ys, Y_RPC, id)) == NULL){ - clicon_debug(1, "%s rpc %s not found", __FUNCTION__, id); if (netconf_missing_element_xml(&xerr, "application", id, "RPC not defined") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } - /* Create an xml message: - * <"rpc">... - * eg + /* 3. Build xml tree with user and rpc: + * */ - /* Create config top-of-tree */ if ((xtop = xml_new("rpc", NULL, NULL)) == NULL) goto done; xbot = xtop; - /* For internal XML protocol: add username attribute for backend access control - */ + /* Here xtop is: */ if ((username = clicon_username_get(h)) != NULL){ if ((xa = xml_new("username", xtop, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); if (xml_value_set(xa, username) < 0) goto done; + /* Here xtop is: */ } - /* XXX: something strange for rpc user */ - if (api_path2xml(oppath, yspec, xtop, YC_SCHEMANODE, &xbot, &y) < 0) + if ((ret = api_path2xml(oppath, yspec, xtop, YC_SCHEMANODE, &xbot, &y)) < 0) goto done; + if (ret == 0){ /* validation failed */ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + clicon_err_reset(); + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + /* Here xtop is: + * xbot is + * 4. Parse input data (arguments): + * JSON: {"example:input":{"x":0}} + * XML: 0 + */ + namespace = xml_find_type_value(xbot, NULL, "xmlns", CX_ATTR); + clicon_debug(1, "%s : 4. Parse input data: %s", __FUNCTION__, data); if (data && strlen(data)){ - /* Parse input data as json or xml into xml */ - if (parse_xml){ - if (xml_parse_string(data, NULL, &xdata) < 0){ - if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) - goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - } - else if (json_parse_str(data, &xdata) < 0){ - if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) - goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((ret = api_operations_post_input(h, r, data, yspec, yrpc, xbot, + pretty, use_xml, parse_xml)) < 0) + goto done; + if (ret == 0) goto ok; - } - yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL); - /* xdata should have format */ - if ((xinput = xpath_first(xdata, "/input")) == NULL){ - xml_name_set(xdata, "input"); - xml_spec_set(xdata, yinput); /* needed for xml_spec_populate */ - if (yinput){ - if ((ret = xml_yang_validate_add(xdata, cbret)) < 0) - goto done; - if (ret == 0){ /* validation failed */ - if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) - goto done; - if ((xe=xpath_first(xerr, "rpc-reply/rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - } - } - else{ - /* Add all input under path */ - x = NULL; - while (xml_child_nr(xinput)){ - x = xml_child_i(xinput, 0); - if (xml_addsub(xbot, x) < 0) - goto done; - } - if (yinput){ - xml_spec_set(xbot, yinput); /* needed for xml_spec_populate */ - /* XXX yinput <-> h ?*/ - if (xml_apply(xbot, CX_ELMNT, xml_spec_populate, yspec) < 0) - goto done; - if ((ret = xml_yang_validate_all(xbot, cbret)) < 0) - goto done; - if (ret == 0){ /* validation failed */ - clicon_debug(1, "%s err: %s", __FUNCTION__, cbuf_get(cbret)); - if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) - goto done; - if ((xe=xpath_first(xerr, "rpc-reply/rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - if ((ret = xml_yang_validate_add(xbot, cbret)) < 0){ - if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) - goto done; - if ((xe=xpath_first(xerr, "rpc-reply/rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - if (xml_apply0(xbot, CX_ELMNT, xml_default, NULL) < 0) - goto done; - } - } } - ret = 0; - xe = NULL; - while ((xe = xml_child_each(xtop, xe, CX_ELMNT)) != NULL) { - /* Look for local (client-side) restconf plugins. */ - if ((ret = rpc_callback_call(h, xe, cbret, r)) < 0) + /* Here xtop is: + 42 */ +#if 0 + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xtop, 0, 0) < 0) goto done; - if (ret == 1){ /* Handled locally */ - if (xml_parse_string(cbuf_get(cbret), NULL, &xret) < 0) - goto done; - /* Local error: return it and quit */ - if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){ - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - } - break; /* Just one if local */ + clicon_debug(1, "%s 5. Translate input args: %s", + __FUNCTION__, cbuf_get(ccc)); } - if (ret == 0){ /* Send to backend */ - if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) +#endif + /* 6. Validate outgoing RPC and fill in defaults */ + if (xml_spec_populate_rpc(h, xtop, yspec) < 0) + goto done; + if ((ret = xml_yang_validate_rpc(xtop, cbret)) < 0) + goto done; + if (ret == 0){ + if (xml_parse_string(cbuf_get(cbret), NULL, &xret) < 0) goto done; - if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){ + if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + /* Here xtop is (default values): + * 4299 + */ +#if 0 + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xtop, 0, 0) < 0) + goto done; + clicon_debug(1, "%s 6. Validate and defaults:%s", __FUNCTION__, cbuf_get(ccc)); + } +#endif + /* 7. Send to RPC handler, either local or backend + * Note (1) xtop is xbot is + * (2) local handler wants and backend wants + */ + /* Look for local (client-side) restconf plugins. + * -1:Error, 0:OK local, 1:OK backend + */ + if ((ret = rpc_callback_call(h, xbot, cbret, r)) < 0) + goto done; + if (ret == 1){ /* Handled locally */ + if (xml_parse_string(cbuf_get(cbret), NULL, &xret) < 0) + goto done; + /* Local error: return it and quit */ + if ((xe = xpath_first(xret, "//rpc-error")) != NULL){ if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; } } - /* Check if RPC output section */ - if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) == NULL){ - /* If the RPC operation is invoked without errors and if the "rpc" or - * "action" statement has no "output" section, the response message - * MUST NOT include a message-body and MUST send a "204 No Content" - * status-line instead. - */ - FCGX_SetExitStatus(204, r->out); /* OK */ - FCGX_FPrintF(r->out, "\r\n"); - goto ok; + else { /* Send to backend */ + if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) + goto done; + if ((xe = xpath_first(xret, "rpc-error")) != NULL){ + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } } - if ((cbx = cbuf_new()) == NULL) + /* 8. Receive reply from local/backend handler as Netconf RPC + * 0 + */ +#if 0 + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xret, 0, 0) < 0) + goto done; + clicon_debug(1, "%s 8. Receive reply:%s", __FUNCTION__, cbuf_get(ccc)); + } +#endif + youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL); + if ((ret = api_operations_post_output(h, r, xret, yspec, youtput, namespace, + pretty, use_xml, &xoutput)) < 0) goto done; - if ((xoutput=xpath_first(xret, "/")) != NULL){ - xml_name_set(xoutput, "output"); - cbuf_reset(cbx); - xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ - if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0) - goto done; - if ((ret = xml_yang_validate_all(xoutput, cbret)) < 0) - goto done; - if (ret == 0){ /* validation failed */ - clicon_debug(1, "%s output validation failed", __FUNCTION__); - if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) - goto done; - if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - if ((ret = xml_yang_validate_add(xoutput, cbret)) < 0) - goto done; - if (ret == 0){ /* validation failed */ - if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) - goto done; - if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - } - /* Sanity check of outgoing XML */ + if (ret == 0) + goto ok; + /* xoutput should now look: 0 */ FCGX_SetExitStatus(200, r->out); /* OK */ FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); FCGX_FPrintF(r->out, "\r\n"); - if (xoutput){ - if (use_xml){ - if (clicon_xml2cbuf(cbx, xoutput, 0, pretty) < 0) - goto done; - } - else - if (xml2json_cbuf(cbx, xoutput, pretty) < 0) - goto done; - FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); - FCGX_FPrintF(r->out, "\r\n\r\n"); + cbuf_reset(cbret); + if (use_xml){ + if (clicon_xml2cbuf(cbret, xoutput, 0, pretty) < 0) + goto done; + /* xoutput should now look: 0 */ } + else{ + if (xml2json_cbuf(cbret, xoutput, pretty) < 0) + goto done; + /* xoutput should now look: {"example:output": {"x":0,"y":42}} */ + } + FCGX_FPrintF(r->out, "%s", cbuf_get(cbret)); + FCGX_FPrintF(r->out, "\r\n\r\n"); ok: retval = 0; done: @@ -1312,16 +1737,12 @@ api_operations_post(clicon_handle h, free(prefix); if (id) free(id); - if (xdata) - xml_free(xdata); if (xtop) xml_free(xtop); if (xret) xml_free(xret); if (xerr) xml_free(xerr); - if (cbx) - cbuf_free(cbx); if (cbret) cbuf_free(cbret); return retval; diff --git a/apps/restconf/restconf_stream.c b/apps/restconf/restconf_stream.c index 08424521..ec20f373 100644 --- a/apps/restconf/restconf_stream.c +++ b/apps/restconf/restconf_stream.c @@ -249,7 +249,7 @@ restconf_stream(clicon_handle h, clicon_err(OE_XML, errno, "cbuf_new"); goto done; } - cprintf(cb, "%s", name); + cprintf(cb, "%s", name); /* Print all fields */ for (i=0; ith_format; else if (strcmp(optname, "pretty") == 0) *value = &th->th_pretty; + else if (strcmp(optname, "sort") == 0) + *value = &th->th_sort; else{ clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname); goto done; @@ -288,6 +292,9 @@ text_setopt(xmldb_handle xh, else if (strcmp(optname, "pretty") == 0){ th->th_pretty = (intptr_t)value; } + else if (strcmp(optname, "sort") == 0){ + th->th_sort = (intptr_t)value; + } else{ clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname); goto done; @@ -358,6 +365,16 @@ xml_copy_marked(cxobj *x0, assert(x0 && x1); yt = xml_spec(x0); /* can be null */ + /* Copy all attributes */ + x = NULL; + while ((x = xml_child_each(x0, x, CX_ATTR)) != NULL) { + name = xml_name(x); + if ((xcopy = xml_new(name, x1, xml_spec(x))) == NULL) + goto done; + if (xml_copy(x, xcopy) < 0) + goto done; + } + /* Go through children to detect any marked nodes: * (3) Special case: key nodes in lists are copied if any * node in list is marked @@ -470,6 +487,10 @@ text_get(xmldb_handle xh, if (singleconfigroot(xt, &xt) < 0) goto done; } + /* XXX: should we validate file if read from disk? + * Argument against: we may want to have a semantically wrong file and wish + * to edit? + */ } /* xt == NULL */ /* Here xt looks like: ... */ @@ -530,12 +551,15 @@ text_get(xmldb_handle xh, /* Add default values (if not set) */ if (xml_apply(xt, CX_ELMNT, xml_default, NULL) < 0) goto done; - /* Order XML children according to YANG */ - if (!xml_child_sort && xml_apply(xt, CX_ELMNT, xml_order, NULL) < 0) - goto done; -#if 0 /* debug */ - if (xml_child_sort && xml_apply0(xt, -1, xml_sort_verify, NULL) < 0) - clicon_log(LOG_NOTICE, "%s: verify failed #2", __FUNCTION__); + /* Order XML children according to YANG. + * XXX: should this be !th->th_sort? + */ + if (!th->th_sort) + if (xml_apply(xt, CX_ELMNT, xml_order, NULL) < 0) + goto done; +#if 1 /* debug */ + if (th->th_sort && xml_apply0(xt, -1, xml_sort_verify, NULL) < 0) + clicon_log(LOG_NOTICE, "%s: sort verify failed #2", __FUNCTION__); #endif if (debug>1) clicon_xml2file(stderr, xt, 0, 1); @@ -555,6 +579,7 @@ text_get(xmldb_handle xh, } /*! Modify a base tree x0 with x1 with yang spec y according to operation op + * @param[in] th text handle * @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 @@ -562,10 +587,11 @@ text_get(xmldb_handle xh, * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc * @param[out] cbret Initialized cligen buffer. Contains return XML or "". * Assume x0 and x1 are same on entry and that y is the spec - * @see put in clixon_keyvalue.c + * @see text_modify_top */ static int -text_modify(cxobj *x0, +text_modify(struct text_handle *th, + cxobj *x0, yang_node *y0, cxobj *x0p, cxobj *x1, @@ -576,6 +602,8 @@ text_modify(cxobj *x0, char *opstr; char *x1name; char *x1cname; /* child name */ + cxobj *x0a; /* attribute */ + cxobj *x1a; /* attribute */ cxobj *x0c; /* base child */ cxobj *x0b; /* base body */ cxobj *x1c; /* mod child */ @@ -607,6 +635,13 @@ text_modify(cxobj *x0, // int iamkey=0; if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL) goto done; + /* Copy xmlns attributes */ + if ((x1a = xml_find_type(x1, NULL, "xmlns", CX_ATTR)) != 0){ + if ((x0a = xml_dup(x1a)) == NULL) + goto done; + if (xml_addsub(x0, x0a) < 0) + goto done; + } #if 0 /* If it is key I dont want to mark it */ if ((iamkey=yang_key_match(y0->yn_parent, x1name)) < 0) @@ -679,6 +714,13 @@ text_modify(cxobj *x0, if (x0==NULL){ if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL) goto done; + /* Copy xmlns attributes */ + if ((x1a = xml_find_type(x1, NULL, "xmlns", CX_ATTR)) != 0){ + if ((x0a = xml_dup(x1a)) == NULL) + goto done; + if (xml_addsub(x0, x0a) < 0) + goto done; + } if (op==OP_NONE) xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */ } @@ -699,7 +741,7 @@ text_modify(cxobj *x0, } /* See if there is a corresponding node in the base tree */ x0c = NULL; - if (match_base_child(x0, x1c, &x0c, yc) < 0) + if (match_base_child(x0, x1c, &x0c, th->th_sort, yc) < 0) goto done; x0vec[i++] = x0c; } @@ -709,7 +751,7 @@ text_modify(cxobj *x0, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); yc = yang_find_datanode(y0, x1cname); - if (text_modify(x0vec[i++], (yang_node*)yc, x0, x1c, op, cbret) < 0) + if (text_modify(th, x0vec[i++], (yang_node*)yc, x0, x1c, op, cbret) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ if (cbuf_len(cbret)) @@ -740,6 +782,7 @@ text_modify(cxobj *x0, } /* text_modify */ /*! Modify a top-level base tree x0 with modification tree x1 + * @param[in] th text handle * @param[in] x0 Base xml tree (can be NULL in add scenarios) * @param[in] x1 xml tree which modifies base * @param[in] yspec Top-level yang spec (if y is NULL) @@ -812,23 +855,19 @@ text_modify_top(struct text_handle *th, goto done; if (ymod != NULL) yc = yang_find_datanode((yang_node*)ymod, x1cname); - if (yc == NULL && _CLICON_XML_NS_ITERATE){ - int i; - for (i=0; iyp_len; i++){ - ymod = yspec->yp_stmt[i]; - if ((yc = yang_find_datanode((yang_node*)ymod, x1cname)) != NULL) - break; - } + if (yc == NULL && !_CLICON_XML_NS_STRICT){ + if (xml_yang_find_non_strict(x1c, yspec, &yc) < 0) + goto done; } if (yc == NULL){ - if (netconf_operation_failed(cbret, "application", "Validation failed")< 0) + if (netconf_unknown_element(cbret, "application", x1cname, "Unassigned yang spec") < 0) goto done; goto ok; } /* See if there is a corresponding node in the base tree */ - if (match_base_child(x0, x1c, &x0c, yc) < 0) + if (match_base_child(x0, x1c, &x0c, th->th_sort, yc) < 0) goto done; - if (text_modify(x0c, (yang_node*)yc, x0, x1c, op, cbret) < 0) + if (text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ if (cbuf_len(cbret)) @@ -840,7 +879,7 @@ text_modify_top(struct text_handle *th, return retval; } /* text_modify_top */ -/*! For containers without presence and no children, remove +/*! For containers without presence and no children(except attrs), remove * @param[in] x XML tree node * See section 7.5.1 in rfc6020bis-02.txt: * No presence: @@ -869,7 +908,7 @@ xml_container_presence(cxobj *x, } /* Mark node that is: container, have no children, dont have presence */ if (y->ys_keyword == Y_CONTAINER && - xml_child_nr(x)==0 && + xml_child_nr_notype(x, CX_ATTR)==0 && yang_find((yang_node*)y, Y_PRESENCE, NULL) == NULL) xml_flag_set(x, XML_FLAG_MARK); /* Mark, remove later */ retval = 0; @@ -964,7 +1003,7 @@ text_put(xmldb_handle xh, goto done; #endif #if 0 /* debug */ - if (xml_child_sort && xml_apply0(x1, -1, xml_sort_verify, NULL) < 0) + if (th->th_sort && xml_apply0(x1, -1, xml_sort_verify, NULL) < 0) clicon_log(LOG_NOTICE, "%s: verify failed #1", __FUNCTION__); #endif /* @@ -975,7 +1014,7 @@ text_put(xmldb_handle xh, goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ if (cbuf_len(cbret)) - goto ok; + goto fail; /* Remove NONE nodes if all subs recursively are also NONE */ if (xml_tree_prune_flagged_sub(x0, XML_FLAG_NONE, 0, NULL) <0) @@ -990,7 +1029,7 @@ text_put(xmldb_handle xh, if (xml_tree_prune_flagged(x0, XML_FLAG_MARK, 1) < 0) goto done; #if 0 /* debug */ - if (xml_child_sort && xml_apply0(x0, -1, xml_sort_verify, NULL) < 0) + if (th->th_sort && xml_apply0(x0, -1, xml_sort_verify, NULL) < 0) clicon_log(LOG_NOTICE, "%s: verify failed #3", __FUNCTION__); #endif /* Write back to datastore cache if first time */ @@ -1025,8 +1064,7 @@ text_put(xmldb_handle xh, } else if (clicon_xml2file(f, x0, 0, th->th_pretty) < 0) goto done; - ok: - retval = 0; + retval = 1; done: if (cbretlocal && cbret) cbuf_free(cbret); @@ -1041,6 +1079,9 @@ text_put(xmldb_handle xh, if (!th->th_cache && x0) xml_free(x0); return retval; + fail: + retval = 0; + goto done; } /*! Copy database from db1 to db2 @@ -1434,7 +1475,7 @@ main(int argc, op = OP_REMOVE; else usage(argv[0]); - if (xmldb_put(h, db, op, NULL, xn, NULL) < 0) + if (xmldb_put(h, db, op, NULL, xn, NULL) < 1) goto done; } else diff --git a/doc/FAQ.md b/doc/FAQ.md index 9ecf46ff..7ab690e0 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -42,9 +42,9 @@ The example: sudo make install ``` -## How do you run Clixon example commands? +## How do I run Clixon example commands? -- Start a backend server: `clixon_backend -Ff /usr/local/etc/example.xml` +- Start a backend server: `clixon_backend -F -s init -f /usr/local/etc/example.xml` - Start a cli session: `clixon_cli -f /usr/local/etc/example.xml` - Start a netconf session: `clixon_netconf -f /usr/local/etc/example.xml` - Start a restconf daemon: `sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data` @@ -72,6 +72,82 @@ grep clicon /etc/group clicon:x:1001:,www-data ``` +## How do I use the CLI? + +The easiest way to use Clixon is via the CLI. Once the backend is started +Example: +``` +clixon_cli -f /usr/local/etc/example.xml +cli> set interfaces interface eth9 ? + description enabled ipv4 + ipv6 link-up-down-trap-enable type +cli> set interfaces interface eth9 type ex:eth +cli> validate +cli> commit +cli> show configuration xml + + + eth9 + ex:eth + true + + +cli> delete interfaces interface eth9 +``` + +## How do I use netconf? + +As an alternative to cli configuration, you can use netconf. Easiest is to just pipe netconf commands to the clixon_netconf application. +Example: +``` +clixon_netconf -qf /usr/local/etc/example.xml +]]>]]> +eth9ex:ethtrue]]>]]> +``` + +However, more useful is to run clixon_netconf as an SSH +subsystem. Register the subsystem in /etc/sshd_config: +``` + Subsystem netconf /usr/local/bin/clixon_netconf -f /usr/local/etc/example.xml +``` +and then invoke it from a client using +``` + ssh -s netconf +``` + +## How do I use restconf? + +You can access clixon via REST API using restconf, such as using +curl. GET, PUT, POST are supported. + +You need a web-server, such as nginx, and start a restconf fcgi +daemon, clixon_restconf. + +For example, using nginx, install, and edit config file: /etc/nginx/sites-available/default: +``` +server { + ... + location /restconf { + fastcgi_pass unix:/www-data/fastcgi_restconf.sock; + include fastcgi_params; + } +} +``` +Start nginx daemon +``` +sudo /etc/init.d/nginx start +``` + +Example: +``` + curl -G http://127.0.0.1/restconf/data/ietf-interfaces:interfaces/interface=eth9/type + [ + { + "ietf-interfaces:type": "ex:eth" + } + ] +``` +Read more in the (restconf)[../apps/restconf] docs. ## What about reference documentation? Clixon uses [Doxygen](http://www.doxygen.nl/index.html) for reference documentation. You need to install doxygen and graphviz on your system. @@ -161,59 +237,6 @@ sudo docker run -td olofhagsand/clixon_example ``` Look in the example documentation for more info. -## How do I use netconf? - -As an alternative to cli configuration, you can use netconf. Easiest is to just pipe netconf commands to the clixon_netconf application. -Example: -``` - echo "]]>]]>" | clixon_netconf -f /usr/local/etc/example.xml -``` - -However, more useful is to run clixon_netconf as an SSH -subsystem. Register the subsystem in /etc/sshd_config: -``` - Subsystem netconf /usr/local/bin/clixon_netconf -f /usr/local/etc/example.xml -``` -and then invoke it from a client using -``` - ssh -s netconf -``` - -## How do I use restconf? - -You can access clixon via REST API using restconf, such as using -curl. GET, PUT, POST are supported. - -You need a web-server, such as nginx, and start a restconf fcgi -daemon, clixon_restconf. - -For example, using nginx, install, and edit config file: /etc/nginx/sites-available/default: -``` -server { - ... - location /restconf { - fastcgi_pass unix:/www-data/fastcgi_restconf.sock; - include fastcgi_params; - } -} -``` -Start nginx daemon -``` -sudo /etc/init.d/nginx start -``` - -Example: -``` - curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=eth9/type - [ - { - "type": "eth" - } - ] -``` -Read more in the (restconf)[../apps/restconf] docs. - - ## Does Clixon support event streams? Yes, Clixon supports event notification streams in the CLI, Netconf and Restconf API:s. @@ -233,7 +256,7 @@ severity major; or via NETCONF: ``` clixon_netconf -qf /usr/local/etc/example.xml -EXAMPLE]]>]]> +EXAMPLE]]>]]> ]]>]]> 2018-09-30T12:44:59.657276faultEthernet0major]]>]]> ... @@ -242,11 +265,11 @@ or via restconf: ``` curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE ``` -Consult (../apps/restconf/README.md) on more information on how to setup a reverse proxy for restconf streams. It is also possible to configure a pub/sub system such as (Nginx Nchan)[https://nchan.io]. +Consult [clixon restconf](../apps/restconf/README.md) on more information on how to setup a reverse proxy for restconf streams. It is also possible to configure a pub/sub system such as [Nginx Nchan](https://nchan.io). ## How should I start the backend daemon? -There are four different backend startup modes. There is differences in running state treatment, ie what state the machine is when you startthe daemon and how loading the configuration affects it: +There are four different backend startup modes. There is differences in running state treatment, ie what state the machine is when you start the daemon and how loading the configuration affects it: - none - Do not touch running state. Typically after crash when running state and db are synched. - init - Initialize running state. Start with a completely clean running state. - running - Commit running db configuration into running state. Typically after reboot if a persistent running db exists. @@ -277,7 +300,7 @@ There are two ways to add extra XML to running database after start. Note that The first way is via a file. Assume you want to add this xml (the config tag is a necessary top-level tag): ``` - extra + extra ``` You add this via the -c option: @@ -289,12 +312,13 @@ The second way is by programming the plugin_reset() in the backend plugin. The example code contains an example on how to do this (see plugin_reset() in example_backend.c). ## I want to program. How do I extend the example? -See [../apps/example] +See [../apps/example](../apps/example) - example.xml - Change the configuration file - The yang specifications - This is the central part. It changes the XML, database and the config cli. - example_cli.cli - Change the fixed part of the CLI commands - example_cli.c - Cli C-commands are placed here. - example_backend.c - Commit and validate functions. +- example_backend_nacm.c - Secondary example plugin (for authorization) - example_netconf.c - Netconf plugin - example_restconf.c - Add restconf authentication, etc. diff --git a/example/README.md b/example/README.md index 7621d9b5..a0a1d621 100644 --- a/example/README.md +++ b/example/README.md @@ -1,19 +1,33 @@ # Clixon example + * [Content](#content) + * [Compile and run](#compile) + * [Using the CLI](#using-the-cli) + * [Using netconf](#using-netconf) + * [Streams](#streams) + * [RPC Operations](#rpc-operations) + * [State data](#state-data) + * [Authentication and NACM](#authentication-and-nacm) + * [Systemd](#systemd) + * [Docker](#docker) + * [Plugins](#plugins) + +## Content + This directory contains a Clixon example which includes a simple example. It contains the following files: -* example.xml The configuration file. See yang/clixon-config@.yang for all available fields. -* example.yang The yang spec of the example. It mainly includes ietf routing and IP modules. -* example_cli.cli CLIgen specification. -* example_cli.c CLI callback plugin containing functions called in the cli file above: a generic callback (`mycallback`) and an RPC (`fib_route_rpc`). -* example_backend.c Backend callback plugin including example of: +* `example.xml` The configuration file. See (yang/clixon-config@.yang)[../yang/clixon-config@2018-10-21.yang] for the documentation of all available fields. +* `example.yang` The yang spec of the example. It mainly includes ietf routing and IP modules. +* `example_cli.cli` CLIgen specification. +* `example_cli.c` CLI callback plugin containing functions called in the cli file above: a generic callback (`mycallback`) and an RPC (`fib_route_rpc`). +* `example_backend.c` Backend callback plugin including example of: * transaction callbacks (validate/commit), * notification, * rpc handler * state-data handler, ie non-config data -* example_backend_nacm.c Secondary backend plugin. Plugins are loaded alphabetically. -* example_restconf.c Restconf callback plugin containing an HTTP basic authentication callback -* example_netconf.c Netconf callback plugin -* Makefile.in Example makefile where plugins are built and installed +* `example_backend_nacm.c` Secondary backend plugin. Plugins are loaded alphabetically. +* `example_restconf.c` Restconf callback plugin containing an HTTP basic authentication callback +* `example_netconf.c` Netconf callback plugin +* `Makefile.in` Example makefile where plugins are built and installed ## Compile and run @@ -47,10 +61,37 @@ Send restconf command curl -G http://127.0.0.1/restconf/data ``` -## Setting data example using netconf +## Using the CLI + +The example CLI allows you to modify and view the data model using `set`, `delete` and `show` via generated code. +There are also many other commands available as examples. View the source file (example_cli.cli)[example_cli.cli] for more details. + +The following example shows how to add an interface in candidate, validate and commit it to running, then look at it (as xml) and finally delete it. +``` +clixon_cli -f /usr/local/etc/example.xml +cli> set interfaces interface eth9 ? + description enabled ipv4 + ipv6 link-up-down-trap-enable type +cli> set interfaces interface eth9 type ex:eth +cli> validate +cli> commit +cli> show configuration xml + + + eth9 + ex:eth + true + + +cli> delete interfaces interface eth9 +``` + +## Using Netconf + +The following example shows how to set data using netconf: ``` - + eth1 true @@ -65,13 +106,13 @@ Send restconf command ]]>]]> ``` -## Getting data using netconf +### Getting data using netconf ``` ]]>]]> ]]>]]> ]]>]]> -]]>]]> -]]>]]> +eth9ex:eth]]>]]> +]]>]]> ]]>]]> ``` @@ -80,23 +121,134 @@ Send restconf command The example has an EXAMPLE stream notification triggering every 5s. To start a notification stream in the session using netconf, create a subscription: ``` -EXAMPLE]]>]]> +EXAMPLE]]>]]> ]]>]]> -Routing notification]]>]]> -Routing notification]]>]]> +2019-01-02T10:20:05.929272faultEthernet0major]]>]]> ... ``` This can also be triggered via the CLI: ``` -cli> notify -cli> Routing notification -Routing notification +clixon_cli -f /usr/local/etc/example.xml +cli> notify +cli> event-class fault; +reportingEntity { + card Ethernet0; +} +severity major; ... +cli> no notify +cli> ``` -Restconf support is also supported, see [../apps/restconf/README.md]. +Restconf support is also supported, see (restc)[../apps/restconf/README.md]. -## Initializing a plugin + +## RPC Operations + +Clixon implements Yang RPC operations by an extension mechanism. The +extension mechanism enables you to add application-specific +operations. It works by adding user-defined callbacks for added +netconf operations. It is possible to use the extension mechanism +independent of the yang rpc construct, but it is recommended. The example includes an example: + +Example using CLI: +``` +cli> rpc ipv4 + rpc-reply { + route { + address-family ipv4; + next-hop { + next-hop-list 2.3.4.5; + } + source-protocol static; + } + } +``` +Netconf: +``` +ipv4]]>]]> +ipv42.3.4.5static]]>]]> +``` +Restconf: +``` +curl -X POST http://localhost/restconf/operations/ietf-routing:fib-route -d '{"ietf-routing:input":{"routing-instance-name":"ipv4"}}' +``` + +### Details + +The example works by creating a netconf rpc call and sending it to the backend: (see the fib_route_rpc() function in [example_cli.c](example_cli.c)). + +In the (example_backend.c)[example_backend.c], a callback is registered (fib_route()) which handles the RPC (this is just dummy data): +``` +static int +fib_route(clicon_handle h, + cxobj *xe, /* Request: */ + cbuf *cbret, /* Reply eg ... */ + void *arg, /* Client session */ + void *regarg) /* Argument given at register */ +{ + cprintf(cbret, "" + "ipv4" + "2.3.4.5" + "static" + ""); + return 0; +} +int +clixon_plugin_init(clicon_handle h) +{ +... + rpc_callback_register(h, fib_route, NULL, "fib-route"); +... +} +``` + +## State data + +Netconf and restconf GET also returns state data(not only configuration data). + +In YANG state data is specified with `config false;`. In the example, +`state` is state data, see (example.yang)[example.yang] + +To return state data, you need to write a backend state data callback +with the name "plugin_statedata" where you return an XML tree with +state. This is then merged with config data by the system. + +A static example of returning state data is in the example. Note that +a real example would poll or get the interface counters via a system +call, as well as use the "xpath" argument to identify the requested +state data. + +## Authentication and NACM +The example contains some stubs for authorization according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341): +* A basic auth HTTP callback, see: example_restconf_credentials() containing three example users: andy, wilma, and guest, according to the examples in Appendix A in [RFC8341](https://tools.ietf.org/html/rfc8341). +* A NACM backend plugin reporting the mandatory NACM state variables. + +## Systemd + +Example systemd files for backend and restconf daemons are found under the systemd directory. Install them under /etc/systemd/system for example. + +## Docker + +Run the example as a docker container and access it from a host CLI as follows: +``` +ID=$(sudo docker run -td olofhagsand/clixon_example) +IP=$(sudo docker inspect -f '{{.NetworkSettings.IPAddress }}' $ID) +clixon_cli -a IPv4 -u $IP -f ./example.xml +``` + +Build the container and push yourself: First change the IMAGE variable in Makefile (eg to "you/clixon_example). Then build and push: +``` +make docker +make push +sudo docker run -ti --rm you/clixon_example +``` + +Note that the configuration database is internal in the container, so +it is deleted if the container is restarted. To make the configuration +database persistent, you need to mount running_db using `-v` + +## Plugins The example includes a restonf, netconf, CLI and two backend plugins. Each plugin is initiated with an API struct followed by a plugin init function. @@ -127,96 +279,3 @@ clixon_plugin_init(clicon_handle h) return &api; /* Return NULL on error */ } ``` - -## Operation data - -Clixon implements Yang RPC operations by an extension mechanism. The -extension mechanism enables you to add application-specific -operations. It works by adding user-defined callbacks for added -netconf operations. It is possible to use the extension mechanism -independent of the yang rpc construct, but it is recommended. The example includes an example: - -Example: -``` -cli> rpc ipv4 - - - -``` - -The example works by creating a netconf rpc call and sending it to the backend: (see the fib_route_rpc() function). -``` - - - ipv4 - - -``` - -In the backend, a callback is registered (fib_route()) which handles the RPC. -``` -static int -fib_route(clicon_handle h, - cxobj *xe, /* Request: */ - cbuf *cbret, /* Reply eg ... */ - void *arg, /* Client session */ - void *regarg) /* Argument given at register */ -{ - cprintf(cbret, ""); - return 0; -} -int -clixon_plugin_init(clicon_handle h) -{ -... - rpc_callback_register(h, fib_route, NULL, "fib-route"); -... -} -``` -## State data - -Netconf and restconf GET also returns state data, in contrast to -config data. -p -In YANG state data is specified with "config false;". In the example, interface-state is state data. - -To return state data, you need to write a backend state data callback -with the name "plugin_statedata" where you return an XML tree with -state. This is then merged with config data by the system. - -A static example of returning state data is in the example. Note that -a real example would poll or get the interface counters via a system -call, as well as use the "xpath" argument to identify the requested -state data. - -## Authentication and NACM -The example contains some stubs for authorization according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341): -* A basic auth HTTP callback, see: example_restconf_credentials() containing three example users: andy, wilma, and guest, according to the examples in Appendix A in [RFC8341](https://tools.ietf.org/html/rfc8341). -* A NACM backend plugin reporting the mandatory NACM state variables. - -## Systemd files - -Example systemd files for backend and restconf daemons are found under the systemd directory. Install them under /etc/systemd/system for example. - -## Docker - -Run the example as a docker container and access it from a host CLI as follows: -``` -ID=$(sudo docker run -td olofhagsand/clixon_example) -IP=$(sudo docker inspect -f '{{.NetworkSettings.IPAddress }}' $ID) -clixon_cli -a IPv4 -u $IP -f ./example.xml -``` - -Build the container and push yourself: First change the IMAGE variable in Makefile (eg to "you/clixon_example). Then build and push: -``` -make docker -make push -sudo docker run -ti --rm you/clixon_example -``` - -Note that the configuration database is internal in the container, so -it is deleted if the container is restarted. To make the configuration -database persistent, you need to mount running_db using `-v` - - - diff --git a/example/example_backend.c b/example/example_backend.c index 35fe20d4..ae1f51d4 100644 --- a/example/example_backend.c +++ b/example/example_backend.c @@ -96,7 +96,7 @@ example_stream_timer(int fd, int retval = -1; clicon_handle h = (clicon_handle)arg; - /* XXX Change to actual netconf notifications */ + /* XXX Change to actual netconf notifications and namespace */ if (stream_notify(h, "EXAMPLE", "faultEthernet0major") < 0) goto done; if (example_stream_timer_setup(h) < 0) @@ -129,7 +129,7 @@ fib_route(clicon_handle h, /* Clicon handle */ void *arg, /* Client session */ void *regarg) /* Argument given at register */ { - cprintf(cbret, "" + cprintf(cbret, "" "ipv4" "2.3.4.5" "static" @@ -147,7 +147,7 @@ route_count(clicon_handle h, void *arg, void *regarg) /* Argument given at register */ { - cprintf(cbret, "42"); + cprintf(cbret, "42"); return 0; } @@ -180,9 +180,17 @@ example_rpc(clicon_handle h, /* Clicon handle */ { int retval = -1; cxobj *x = NULL; + char *namespace; + /* get namespace from rpc name, return back in each output parameter */ + if ((namespace = xml_find_type_value(xe, NULL, "xmlns", CX_ATTR)) == NULL){ + clicon_err(OE_XML, ENOENT, "No namespace given in rpc %s", xml_name(xe)); + goto done; + } cprintf(cbret, ""); while ((x = xml_child_each(xe, x, CX_ELMNT)) != NULL) { + if (xmlns_set(x, NULL, namespace) < 0) + goto done; if (clicon_xml2cbuf(cbret, x, 0, 0) < 0) goto done; } @@ -222,7 +230,7 @@ example_statedata(clicon_handle h, * Note this state needs to be accomanied by yang snippet * above */ - if (xml_parse_string("" + if (xml_parse_string("" "42" "", NULL, &xstate) < 0) goto done; @@ -251,17 +259,22 @@ example_reset(clicon_handle h, { int retval = -1; cxobj *xt = NULL; + int ret; - if (xml_parse_string("" + if (xml_parse_string("" "loex:loopback" "", NULL, &xt) < 0) goto done; - /* Replace parent w fiorst child */ + /* Replace parent w first child */ if (xml_rootchild(xt, 0, &xt) < 0) goto done; /* Merge user reset state */ - if (xmldb_put(h, (char*)db, OP_MERGE, xt, NULL) < 0) + if ((ret = xmldb_put(h, (char*)db, OP_MERGE, xt, NULL)) < 0) goto done; + if (ret == 0){ + clicon_err(OE_XML, 0, "Error when writing to XML database"); + goto done; + } retval = 0; done: if (xt != NULL) diff --git a/example/example_cli.c b/example/example_cli.c index 987aae30..01a375a3 100644 --- a/example/example_cli.c +++ b/example/example_cli.c @@ -110,7 +110,7 @@ fib_route_rpc(clicon_handle h, goto done; } /* Print result */ - xml_print(stdout, xml_child_i(xret, 0)); + xml2txt(stdout, xml_child_i(xret, 0), 1); retval = 0; done: if (xret) diff --git a/example/example_cli.cli b/example/example_cli.cli index 302a6cf3..892c687a 100644 --- a/example/example_cli.cli +++ b/example/example_cli.cli @@ -24,7 +24,7 @@ debug("Debugging parts of the system"), cli_debug_cli((int32)1);{ } copy("Copy and create a new object") { interface("Copy interface"){ - ("name of interface to copy from") to("Copy to interface") ("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s='%s']","name","name","toname"); + ("name of interface to copy from") to("Copy to interface") ("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s='%s']","name","name","toname"); } } discard("Discard edits (rollback 0)"), discard_changes(); diff --git a/example/example_netconf.c b/example/example_netconf.c index 9993e851..b70128de 100644 --- a/example/example_netconf.c +++ b/example/example_netconf.c @@ -71,7 +71,7 @@ int netconf_client_rpc(clicon_handle h, void *regarg) { clicon_debug(1, "%s restconf", __FUNCTION__); - cprintf(cbret, "ok"); + cprintf(cbret, "ok"); return 0; } diff --git a/example/example_restconf.c b/example/example_restconf.c index 43771662..ec0e8d27 100644 --- a/example/example_restconf.c +++ b/example/example_restconf.c @@ -275,7 +275,7 @@ restconf_client_rpc(clicon_handle h, { // FCGX_Request *r = (FCGX_Request *)arg; clicon_debug(1, "%s", __FUNCTION__); - cprintf(cbret, "ok"); + cprintf(cbret, "ok"); return 0; } diff --git a/include/clixon_custom.h b/include/clixon_custom.h index cbb3f1df..8b7c5ae6 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -51,11 +51,6 @@ int strverscmp (__const char *__s1, __const char *__s2); */ #define XMLNS_YANG_ONLY 1 -/* Set for full XML namespace code in XML, NETCONF and YANG - * Experimental - */ -#undef ENABLE_XMLNS - /* If set, patch all CLI spec calls to @datamodel:tree to @datamodel. * This is a backward compatible fix for 3.9 for CLIgen specification files * using model generation (CLIXON_CLI_GENMODEL). diff --git a/lib/clixon/clixon_json.h b/lib/clixon/clixon_json.h index 68b19559..0a644680 100644 --- a/lib/clixon/clixon_json.h +++ b/lib/clixon/clixon_json.h @@ -43,6 +43,7 @@ int xml2json_cbuf(cbuf *cb, cxobj *x, int pretty); int xml2json_cbuf_vec(cbuf *cb, cxobj **vec, size_t veclen, int pretty); int xml2json(FILE *f, cxobj *x, int pretty); int xml2json_vec(FILE *f, cxobj **vec, size_t veclen, int pretty); +int json2xml_ns(yang_spec *yspec, cxobj *x, cxobj **xerr); int json_parse_str(char *str, cxobj **xt); int json_parse_file(int fd, yang_spec *yspec, cxobj **xt); diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index 8f1b9da0..a615c663 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -52,7 +52,8 @@ int netconf_bad_element(cbuf *cb, char *type, char *info, char *element); int netconf_bad_element_xml(cxobj **xret, char *type, char *info, char *element); int netconf_unknown_element(cbuf *cb, char *type, char *element, char *message); int netconf_unknown_element_xml(cxobj **xret, char *type, char *element, char *message); -int netconf_unknown_namespace(cbuf *cb, char *type, char *info, char *message); +int netconf_unknown_namespace(cbuf *cb, char *type, char *namespace, char *message); +int netconf_unknown_namespace_xml(cxobj **xret, char *type, char *namespace, char *message); int netconf_access_denied(cbuf *cb, char *type, char *message); int netconf_access_denied_xml(cxobj **xret, char *type, char *message); int netconf_lock_denied(cbuf *cb, char *info, char *message); diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index b5cb55c3..a849c6cd 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -153,6 +153,9 @@ static inline char *clicon_xmldb_dir(clicon_handle h){ static inline char *clicon_xmldb_plugin(clicon_handle h){ return clicon_option_str(h, "CLICON_XMLDB_PLUGIN"); } +static inline int clicon_xml_sort(clicon_handle h){ + return clicon_option_bool(h, "CLICON_XML_SORT"); +} /*-- Specific option access functions for YANG options w type conversion--*/ int clicon_cli_genmodel(clicon_handle h); diff --git a/lib/clixon/clixon_string.h b/lib/clixon/clixon_string.h index cebb0871..dd2eb290 100644 --- a/lib/clixon/clixon_string.h +++ b/lib/clixon/clixon_string.h @@ -86,7 +86,7 @@ int xml_chardata_encode(char **escp, char *fmt, ...); int uri_percent_decode(char *enc, char **str); const char *clicon_int2str(const map_str2int *mstab, int i); int clicon_str2int(const map_str2int *mstab, char *str); - +int nodeid_split(char *nodeid, char **prefix, char **id); #ifndef HAVE_STRNDUP char *clicon_strndup (const char *, size_t); #endif /* ! HAVE_STRNDUP */ diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index 68f332ab..2117bfef 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -45,6 +45,8 @@ * namespace (rfc6241 3.1) */ #define DEFAULT_XML_RPC_NAMESPACE "urn:ietf:params:xml:ns:netconf:base:1.0" +/* default namespace statement, such as in */ +#define DEFAULT_XMLNS "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"" /* * Types @@ -84,14 +86,13 @@ typedef int (xml_applyfn_t)(cxobj *x, void *arg); #define XML_FLAG_CHANGE 0x08 /* Node is changed (commits) or child changed rec */ #define XML_FLAG_NONE 0x10 /* Node is added as NONE */ - /* Iterate through modules to find the matching datanode * or rpc if no xmlns attribute specifies namespace. - * This is loose semantics of finding namespaces. + * This is lazy non-strict semantics of finding namespaces. * And it is wrong, but is the way Clixon originally was written." - * @see CLICON_XML_NS_ITERATE clixon configure option + * @see CLICON_XML_NS_STRICT clixon configure option */ -extern int _CLICON_XML_NS_ITERATE; +extern int _CLICON_XML_NS_STRICT; /* * Prototypes @@ -102,6 +103,7 @@ int xml_name_set(cxobj *xn, char *name); char *xml_prefix(cxobj *xn); int xml_prefix_set(cxobj *xn, char *name); int xml2ns(cxobj *x, char *localname, char **namespace); +int xmlns_set(cxobj *x, char *prefix, char *namespace); cxobj *xml_parent(cxobj *xn); int xml_parent_set(cxobj *xn, cxobj *parent); @@ -117,7 +119,9 @@ int xml_type_set(cxobj *xn, enum cxobj_type type); int xml_child_nr(cxobj *xn); int xml_child_nr_type(cxobj *xn, enum cxobj_type type); +int xml_child_nr_notype(cxobj *xn, enum cxobj_type type); cxobj *xml_child_i(cxobj *xn, int i); +cxobj *xml_child_i_type(cxobj *xn, int i, enum cxobj_type type); cxobj *xml_child_i_set(cxobj *xt, int i, cxobj *xc); cxobj *xml_child_each(cxobj *xparent, cxobj *xprev, enum cxobj_type type); @@ -139,6 +143,7 @@ char *xml_body(cxobj *xn); cxobj *xml_body_get(cxobj *xn); char *xml_find_type_value(cxobj *xn_parent, char *prefix, char *name, enum cxobj_type type); +cxobj *xml_find_type(cxobj *xn_parent, char *prefix, char *name, enum cxobj_type type); char *xml_find_value(cxobj *xn_parent, char *name); char *xml_find_body(cxobj *xn, char *name); cxobj *xml_find_body_obj(cxobj *xt, char *name, char *val); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index dba82884..9eba5bfd 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -43,10 +43,13 @@ */ int xml2txt(FILE *f, cxobj *x, int level); int xml2cli(FILE *f, cxobj *x, char *prepend, enum genmodel_type gt); +int xml_yang_root(cxobj *x, cxobj **xr); +int xmlns_assign(cxobj *x); int xml_yang_validate_rpc(cxobj *xrpc, cbuf *cbret); int xml_yang_validate_add(cxobj *xt, cbuf *cbret); int xml_yang_validate_all(cxobj *xt, cbuf *cbret); int xml_yang_validate_all_top(cxobj *xt, cbuf *cbret); +int xml_yang_find_non_strict(cxobj *x, yang_spec *yspec, yang_stmt **y); int xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0); int cvec2xml_1(cvec *cvv, char *toptag, cxobj *xp, cxobj **xt0); int xml_diff(yang_spec *yspec, cxobj *xt1, cxobj *xt2, @@ -64,8 +67,7 @@ int xml_sanity(cxobj *x, void *arg); int xml_non_config_data(cxobj *xt, void *arg); int xml_spec_populate_rpc(clicon_handle h, cxobj *x, yang_spec *yspec); 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_path2xpath(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath); int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop, yang_class nodeclass, cxobj **xpathp, yang_node **ypathp); int xml_merge(cxobj *x0, cxobj *x1, yang_spec *yspec, char **reason); diff --git a/lib/clixon/clixon_xml_sort.h b/lib/clixon/clixon_xml_sort.h index 620456a0..c4ffeab3 100644 --- a/lib/clixon/clixon_xml_sort.h +++ b/lib/clixon/clixon_xml_sort.h @@ -37,14 +37,16 @@ #define _CLIXON_XML_SORT_H /* Sort and binary search of XML children - * Experimental + * XXX This variable is a kludge since low-level functions xml_merge/xml_diff calls + * match_base_child without handle + * @see clicon_xml_sort */ extern int xml_child_sort; /* * Prototypes */ -int xml_child_spec(char *name, cxobj *xp, yang_spec *yspec, yang_stmt **yp); +int xml_child_spec(cxobj *x, cxobj *xp, yang_spec *yspec, yang_stmt **yp); int xml_cmp(const void* arg1, const void* arg2); int xml_sort(cxobj *x0, void *arg); cxobj *xml_search(cxobj *x, char *name, int yangi, enum rfc_6020 keyword, int keynr, char **keyvec, char **keyval); @@ -53,6 +55,6 @@ int xml_insert_pos(cxobj *x0, char *name, int yangi, enum rfc_6020 keyword, int upper); cxobj *xml_match(cxobj *x0, char *name, enum rfc_6020 keyword, int keynr, char **keyvec, char **keyval); int xml_sort_verify(cxobj *x, void *arg); -int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, yang_stmt *yc); +int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, int xml_sort, yang_stmt *yc); #endif /* _CLIXON_XML_SORT_H */ diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index e9aee2e6..a9291583 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -252,17 +252,16 @@ yang_stmt *yn_each(yang_node *yn, yang_stmt *ys); char *yang_key2str(int keyword); char *yarg_prefix(yang_stmt *ys); char *yarg_id(yang_stmt *ys); -int yang_nodeid_split(char *nodeid, char **prefix, char **id); int ys_module_by_xml(yang_spec *ysp, struct xml *xt, yang_stmt **ymodp); yang_stmt *ys_module(yang_stmt *ys); yang_spec *ys_spec(yang_stmt *ys); yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix); yang_stmt *yang_find_module_by_namespace(yang_spec *yspec, char *namespace); +yang_stmt *yang_find_module_by_name(yang_spec *yspec, char *name); yang_stmt *yang_find(yang_node *yn, int keyword, const char *argument); int yang_match(yang_node *yn, int keyword, char *argument); yang_stmt *yang_find_datanode(yang_node *yn, char *argument); yang_stmt *yang_find_schemanode(yang_node *yn, char *argument); -yang_stmt *yang_find_topnode(yang_spec *ysp, char *name, yang_class class); char *yang_find_myprefix(yang_stmt *ys); char *yang_find_mynamespace(yang_stmt *ys); int yang_order(yang_stmt *y); diff --git a/lib/src/clixon_err.c b/lib/src/clixon_err.c index aa4576df..6ec992c6 100644 --- a/lib/src/clixon_err.c +++ b/lib/src/clixon_err.c @@ -140,6 +140,7 @@ clicon_err_reset(void) * @param[in] err Error number, typically errno * @param[in] suberr Sub-error number * @param[in] reason Error string, format with argv + * @see clicon_err_reser Resetting the global error variables. */ int clicon_err_fn(const char *fn, diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index 06aa71c9..1cd99af1 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -55,10 +55,12 @@ #include "clixon_err.h" #include "clixon_log.h" #include "clixon_queue.h" +#include "clixon_string.h" #include "clixon_hash.h" #include "clixon_handle.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_netconf_lib.h" #include "clixon_json.h" #include "clixon_json_parse.h" @@ -90,43 +92,31 @@ enum childtype{ ANY_CHILD, /* eg or */ }; -/*! Number of children EXCEPT attributes - * @param[in] xn xml node - * @retval number of children in XML tree (except children of type CX_ATTR) - * @see xml_child_nr - */ -static int -xml_child_nr_noattr(cxobj *xn) -{ - cxobj *x = NULL; - int nr = 0; - - while ((x = xml_child_each(xn, x, -1)) != NULL) { - if (xml_type(x) != CX_ATTR) - nr++; - } - return nr; -} - /*! x is element and has exactly one child which in turn has none * remove attributes from x * Clone from clixon_xml_map.c */ static enum childtype -childtype(cxobj *x) +child_type(cxobj *x) { - cxobj *xc1; /* the only child of x */ + cxobj *xc; /* the only child of x */ int clen; /* nr of children */ - clen = xml_child_nr_noattr(x); + clen = xml_child_nr_notype(x, CX_ATTR); if (xml_type(x) != CX_ELMNT) return -1; /* n/a */ if (clen == 0) return NULL_CHILD; if (clen > 1) return ANY_CHILD; - xc1 = xml_child_i(x, 0); /* From here exactly one child */ - if (xml_child_nr_noattr(xc1) == 0 && xml_type(xc1)==CX_BODY) + /* From here exactly one noattr child, get it */ + xc = NULL; + while ((xc = xml_child_each(x, xc, -1)) != NULL) + if (xml_type(xc) != CX_ATTR) + break; + if (xc == NULL) + return -2; /* n/a */ + if (xml_child_nr_notype(xc, CX_ATTR) == 0 && xml_type(xc)==CX_BODY) return BODY_CHILD; else return ANY_CHILD; @@ -175,6 +165,9 @@ arraytype2str(enum array_element_type lt) return ""; } +/*! Check typeof x in array + * Some complexity when x is in different namespaces + */ static enum array_element_type array_eval(cxobj *xprev, cxobj *x, @@ -184,7 +177,10 @@ array_eval(cxobj *xprev, int eqprev=0; int eqnext=0; yang_stmt *ys; + char *nsx; /* namespace of x */ + char *ns2; + nsx = xml_find_type_value(x, NULL, "xmlns", CX_ATTR); if (xml_type(x)!=CX_ELMNT){ array=BODY_ARRAY; goto done; @@ -192,12 +188,18 @@ array_eval(cxobj *xprev, ys = xml_spec(x); if (xnext && xml_type(xnext)==CX_ELMNT && - strcmp(xml_name(x),xml_name(xnext))==0) - eqnext++; + strcmp(xml_name(x),xml_name(xnext))==0){ + ns2 = xml_find_type_value(xnext, NULL, "xmlns", CX_ATTR); + if (nsx && ns2 && strcmp(nsx,ns2)==0) + eqnext++; + } if (xprev && xml_type(xprev)==CX_ELMNT && - strcmp(xml_name(x),xml_name(xprev))==0) - eqprev++; + strcmp(xml_name(x),xml_name(xprev))==0){ + ns2 = xml_find_type_value(xprev, NULL, "xmlns", CX_ATTR); + if (nsx && ns2 && strcmp(nsx,ns2)==0) + eqprev++; + } if (eqprev && eqnext) array = MIDDLE_ARRAY; else if (eqprev) @@ -316,8 +318,8 @@ xml2json1_cbuf(cbuf *cb, * module name */ prefix = xml_prefix(x); - if (xml2ns(x, prefix, &namespace) < 0) - goto done; + namespace = xml_find_type_value(x, prefix, "xmlns", CX_ATTR); + if ((ys = xml_spec(x)) != NULL) /* yang spec associated with x */ yspec = ys_spec(ys); /* Find module name associated with namspace URI */ @@ -325,7 +327,7 @@ xml2json1_cbuf(cbuf *cb, (ymod = yang_find_module_by_namespace(yspec, namespace)) != NULL){ modname = ymod->ys_argument; } - childt = childtype(x); + childt = child_type(x); if (pretty==2) cprintf(cb, "#%s_array, %s_child ", arraytype2str(arraytype), @@ -442,7 +444,7 @@ xml2json1_cbuf(cbuf *cb, xc_arraytype, level+1, pretty, 0, bodystr0) < 0) goto done; - if (i%s" "%s" - "%s" + "<%s>%s" "error", - type, tag, element) < 0) + type, tag, infotag, element, infotag) < 0) goto done; if (message && xml_parse_va(&xerr, NULL, "%s", message) < 0) @@ -363,7 +364,8 @@ netconf_missing_element(cbuf *cb, int retval = -1; cxobj *xret = NULL; - if (netconf_element_xml_common(&xret, type, "missing-element", element, message) < 0) + if (netconf_common_xml(&xret, type, "missing-element", + "bad-element", element, message) < 0) goto done; if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) goto done; @@ -387,7 +389,8 @@ netconf_missing_element_xml(cxobj **xret, char *element, char *message) { - return netconf_element_xml_common(xret, type, "missing-element", element, message); + return netconf_common_xml(xret, type, "missing-element", + "bad-element", element, message); } /*! Create Netconf bad-element error XML tree according to RFC 6241 App A @@ -408,7 +411,8 @@ netconf_bad_element(cbuf *cb, int retval = -1; cxobj *xret = NULL; - if (netconf_element_xml_common(&xret, type, "bad-element", element, message) < 0) + if (netconf_common_xml(&xret, type, "bad-element", + "bad-element",element, message) < 0) goto done; if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) goto done; @@ -424,7 +428,7 @@ netconf_bad_element_xml(cxobj **xret, char *element, char *message) { - return netconf_element_xml_common(xret, type, "bad-element", element, message); + return netconf_common_xml(xret, type, "bad-element", "bad-element", element, message); } /*! Create Netconf unknown-element error XML tree according to RFC 6241 App A @@ -444,7 +448,8 @@ netconf_unknown_element(cbuf *cb, int retval = -1; cxobj *xret = NULL; - if (netconf_element_xml_common(&xret, type, "unknown-element", element, message) < 0) + if (netconf_common_xml(&xret, type, "unknown-element", + "bad-element", element, message) < 0) goto done; if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) goto done; @@ -454,13 +459,23 @@ netconf_unknown_element(cbuf *cb, xml_free(xret); return retval; } + +/*! Create Netconf unknown-element error XML tree according to RFC 6241 App A + * + * An unexpected element is present. + * @param[out] xret XML buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] element Bad element name + * @param[in] message Error message + */ int netconf_unknown_element_xml(cxobj **xret, char *type, char *element, char *message) { - return netconf_element_xml_common(xret, type, "unknown-element", element, message); + return netconf_common_xml(xret, type, "unknown-element", + "bad-element", element, message); } /*! Create Netconf unknown-namespace error XML tree according to RFC 6241 App A @@ -474,35 +489,32 @@ netconf_unknown_element_xml(cxobj **xret, int netconf_unknown_namespace(cbuf *cb, char *type, - char *info, + char *namespace, char *message) { - int retval = -1; - char *encstr = NULL; + int retval = -1; + cxobj *xret = NULL; - if (cprintf(cb, "" - "%s" - "unknown-namespace" - "%s" - "error", - type, info) <0) - goto err; - if (message){ - if (xml_chardata_encode(&encstr, "%s", message) < 0) - goto done; - if (cprintf(cb, "%s", encstr) < 0) - goto err; - } - if (cprintf(cb, "") <0) - goto err; + if (netconf_common_xml(&xret, type, "unknown-namespace", + "bad-namespace", namespace, message) < 0) + goto done; + if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) + goto done; retval = 0; done: - if (encstr) - free(encstr); + if (xret) + xml_free(xret); return retval; - err: - clicon_err(OE_XML, errno, "cprintf"); - goto done; +} + +int +netconf_unknown_namespace_xml(cxobj **xret, + char *type, + char *namespace, + char *message) +{ + return netconf_common_xml(xret, type, "unknown-namespace", + "bad-namespace", namespace, message); } /*! Create Netconf access-denied error cbuf according to RFC 6241 App A @@ -989,7 +1001,7 @@ netconf_trymerge(cxobj *x, } /*! Load ietf netconf yang module and set enabled features - * The features added are: + * The features added are (in order): * candidate (8.3) * validate (8.6) * startup (8.7) diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 45c3948b..e6410ad8 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -253,9 +253,8 @@ clicon_options_main(clicon_handle h, clicon_err(OE_CFG, 0, "%s: suffix %s not recognized (Run ./configure --with-config-compat?)", configfile, suffix); goto done; } -#if 1 /* XXX Kludge to low-level functions to iterate over namspaces or not */ - _CLICON_XML_NS_ITERATE = 1; -#endif + /* XXX Kludge to low-level functions to search for xml in all yang modules */ + _CLICON_XML_NS_STRICT = 0; /* Read configfile first without yangspec, for bootstrapping */ if (parse_configfile(h, configfile, yspec, &xconfig) < 0) goto done; @@ -281,9 +280,8 @@ clicon_options_main(clicon_handle h, xml_child_sort = 1; else xml_child_sort = 0; -#if 1 /* XXX Kludge to low-level functions to iterate over namspaces or not */ - _CLICON_XML_NS_ITERATE = clicon_option_bool(h, "CLICON_XML_NS_ITERATE"); -#endif + /* XXX Kludge to low-level functions to search for xml in all yang modules */ + _CLICON_XML_NS_STRICT = clicon_option_bool(h, "CLICON_XML_NS_STRICT"); retval = 0; done: return retval; diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index aa3ac4ac..396a0665 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -351,7 +351,7 @@ clicon_rpc_edit_config(clicon_handle h, if ((cb = cbuf_new()) == NULL) goto done; - cprintf(cb, "<%s/>", db); @@ -787,7 +787,7 @@ clicon_rpc_create_subscription(clicon_handle h, char *username; username = clicon_username_get(h); - if ((msg = clicon_msg_encode("" + if ((msg = clicon_msg_encode("" "%s" "" "", diff --git a/lib/src/clixon_string.c b/lib/src/clixon_string.c index 480805fb..a7c05f39 100644 --- a/lib/src/clixon_string.c +++ b/lib/src/clixon_string.c @@ -562,6 +562,54 @@ clicon_str2int(const map_str2int *mstab, return -1; } +/*! Split colon-separated node identifier into prefix and name + * @param[in] node-id + * @param[out] prefix Malloced string. May be NULL. + * @param[out] id Malloced identifier. + * @retval 0 OK + * @retval -1 Error + * @code + * char *prefix = NULL; + * char *id = NULL; + * if (nodeid_split(nodeid, &prefix, &id) < 0) + * goto done; + * if (prefix) + * free(prefix); + * if (id) + * free(id); + * @note caller need to free id and prefix after use + */ +int +nodeid_split(char *nodeid, + char **prefix, + char **id) +{ + int retval = -1; + char *str; + + if ((str = strchr(nodeid, ':')) == NULL){ + if ((*id = strdup(nodeid)) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + } + } + else{ + if ((*prefix = strdup(nodeid)) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + } + (*prefix)[str-nodeid] = '\0'; + str++; + if ((*id = strdup(str)) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + } + } + retval = 0; + done: + return retval; +} + /*! strndup() for systems without it, such as xBSD */ #ifndef HAVE_STRNDUP diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 59292ba9..6849e94d 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -62,6 +62,8 @@ #include "clixon_log.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_options.h" /* xml_spec_populate */ +#include "clixon_xml_map.h" /* xml_spec_populate */ #include "clixon_xml_sort.h" #include "clixon_xml_parse.h" @@ -133,9 +135,9 @@ struct xml{ * or rpc if no xmlns attribute specifies namespace. * This is loose semantics of finding namespaces. * And it is wrong, but is the way Clixon originally was written." - * @see CLICON_XML_NS_ITERATE clixon configure option + * @see CLICON_XML_NS_STRICT clixon configure option */ -int _CLICON_XML_NS_ITERATE = 0; +int _CLICON_XML_NS_STRICT = 1; /* Mapping between xml type <--> string */ static const map_str2int xsmap[] = { @@ -226,8 +228,7 @@ xml_prefix_set(cxobj *xn, return 0; } - -/*! Given an xml tree return URI namespace: default or localname given +/*! Given an xml tree return URI namespace recursively : default or localname given * * Given an XML tree and a prefix (or NULL) return URI namespace. * @param[in] x XML tree @@ -235,7 +236,7 @@ xml_prefix_set(cxobj *xn, * @param[out] namespace URI namespace (or NULL). Note pointer into xml tree * @retval 0 OK * @retval -1 Error - * @see xmlns_check XXX coordinate + * @see xmlns_check XXX can these be merged? */ int xml2ns(cxobj *x, @@ -246,9 +247,9 @@ xml2ns(cxobj *x, char *ns; cxobj *xp; - if (prefix != NULL) /* xmlns: */ + if (prefix != NULL) /* xmlns:="" */ ns = xml_find_type_value(x, "xmlns", prefix, CX_ATTR); - else /* default ns */ + else /* xmlns="" */ ns = xml_find_type_value(x, NULL, "xmlns", CX_ATTR); /* namespace not found, try parent */ @@ -270,6 +271,40 @@ xml2ns(cxobj *x, return retval; } +/*! Add a namespace attribute to an XML node, either default or specific prefix + * @param[in] x XML tree + * @param[in] prefix prefix/ns localname. If NULL then set default xmlns + * @param[out] namespace URI namespace (or NULL). Will be copied + * @retval 0 OK + * @retval -1 Error + * @see xml2ns + */ +int +xmlns_set(cxobj *x, + char *prefix, + char *namespace) +{ + int retval = -1; + cxobj *xa; + + if (prefix != NULL){ /* xmlns:="" */ + if ((xa = xml_new(prefix, x, NULL)) == NULL) + goto done; + if (xml_prefix_set(xa, "xmlns") < 0) + goto done; + } + else{ /* xmlns="" */ + if ((xa = xml_new("xmlns", x, NULL)) == NULL) + goto done; + xml_type_set(xa, CX_ATTR); + } + if (xml_value_set(xa, namespace) < 0) + goto done; + retval = 0; + done: + return retval; +} + /*! See if xmlns:[=] exists, if so return * * @param[in] xn XML node @@ -483,6 +518,8 @@ xml_type_set(cxobj *xn, /*! Get number of children * @param[in] xn xml node * @retval number of children in XML tree + * @see xml_child_nr_type + * @see xml_child_nr_notype */ int xml_child_nr(cxobj *xn) @@ -490,10 +527,33 @@ xml_child_nr(cxobj *xn) return xn->x_childvec_len; } +/*! Get number of children of EXCEPT specific type + * @param[in] xn xml node + * @param[in] type XML type or -1 for all + * @retval number of typed children in XML tree (except type) + * @see xml_child_nr + * @see xml_child_nr_type + */ +int +xml_child_nr_notype(cxobj *xn, + enum cxobj_type type) +{ + cxobj *x = NULL; + int nr = 0; + + while ((x = xml_child_each(xn, x, -1)) != NULL) { + if (xml_type(x) != type) + nr++; + } + return nr; +} + /*! Get number of children of specific type * @param[in] xn xml node * @param[in] type XML type or -1 for all * @retval number of typed children in XML tree + * @see xml_child_nr + * @see xml_child_nr_notype */ int xml_child_nr_type(cxobj *xn, @@ -521,6 +581,28 @@ xml_child_i(cxobj *xn, return NULL; } +/*! Get a specific child of a specific type + * @param[in] xn xml node + * @param[in] i the number of the child of specific type + * @param[in] type Child type + * @retval child in XML tree, or NULL if no such child, or empty child + * @see xml_child_i + */ +cxobj * +xml_child_i_type(cxobj *xn, + int i, + enum cxobj_type type) +{ + cxobj *x = NULL; + int it = 0; + + while ((x = xml_child_each(xn, x, type)) != NULL) { + if (x->x_type == type && (i == it++)) + return x; + } + return NULL; +} + /*! Set specific child * @param[in] xn xml node * @param[in] i the number of the child, eg order in children vector @@ -938,7 +1020,7 @@ xml_body_get(cxobj *xt) return NULL; } -/*! Find and return the value of an xml child of specific type +/*! Find and return the value of an xml child of specific type given prefix and name * * The value can be of an attribute only * @param[in] xt xml tree node @@ -950,6 +1032,7 @@ xml_body_get(cxobj *xt) * char *str = xml_find_type_value(x, "prefix", "name", CX_ATTR); * @endcode * @note, make a copy of the return value to use it properly + * @see xml_find_type return the xml object * @see xml_find_value where a body can be found as well */ char * @@ -957,6 +1040,32 @@ xml_find_type_value(cxobj *xt, char *prefix, char *name, enum cxobj_type type) +{ + cxobj *x; + + if ((x = xml_find_type(xt, prefix, name, type)) != NULL) + return xml_value(x); + return NULL; +} + +/*! Find and return the xml child of specific type given prefix and name + * + * The value can be of an attribute only + * @param[in] xt xml tree node + * @param[in] prefix Prefix (namespace local name) or NULL + * @param[in] name name of xml tree node (eg attr name or "body") + * @retval val Pointer to the name string + * @retval NULL No such node or no value in node + * @code + * cxobj *x = xml_find_type(x, "prefix", "name", CX_ATTR); + * @endcode + * @see xml_find_value where a body can be found as well + */ +cxobj * +xml_find_type(cxobj *xt, + char *prefix, + char *name, + enum cxobj_type type) { cxobj *x = NULL; int pmatch; /* prefix match */ @@ -969,7 +1078,7 @@ xml_find_type_value(cxobj *xt, else pmatch = 1; if (pmatch && strcmp(name, xml_name(x)) == 0) - return xml_value(x); + return x; } return NULL; } @@ -1397,6 +1506,8 @@ _xml_parse(const char *str, goto done; /* Sort the complete tree after parsing */ if (yspec){ + if (xml_apply0(xt, CX_ELMNT, xml_spec_populate, yspec) < 0) + goto done; if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0) goto done; if (xml_apply0(xt, -1, xml_sort_verify, NULL) < 0) @@ -1592,7 +1703,7 @@ xml_parse_va(cxobj **xtop, return retval; } -/*! Copy single xml node without copying children +/*! Copy single xml node frm x0 to x1 without copying children */ int xml_copy_one(cxobj *x0, diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index c3a4126a..32aab817 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -380,7 +380,8 @@ xmldb_get(clicon_handle h, * @param[in] op Top-level operation, can be superceded by other op in tree * @param[in] xt xml-tree. Top-level symbol is dummy * @param[out] cbret Initialized cligen buffer or NULL. On exit contains XML or "". - * @retval 0 OK + * @retval 1 OK + * @retval 0 Failed, cbret contains error xml message * @retval -1 Error * The xml may contain the "operation" attribute which defines the operation. * @code @@ -388,8 +389,10 @@ xmldb_get(clicon_handle h, * cxobj *xret = NULL; * if (xml_parse_string("17", yspec, &xt) < 0) * err; - * if (xmldb_put(xh, "running", OP_MERGE, xt, cbret) < 0) + * if ((ret = xmldb_put(xh, "running", OP_MERGE, xt, cbret)) < 0) * err; + * if (ret==0) + * cbret contains netconf error message * @endcode * @note that you can add both config data and state data. In comparison, * xmldb_get has a parameter to get config data only. diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 2644f494..5d210644 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -50,6 +50,7 @@ * +---------+ * | file | * +---------+ + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 */ #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ @@ -95,14 +96,20 @@ static int tleaf(cxobj *x) { - cxobj *c; + cxobj *xc; if (xml_type(x) != CX_ELMNT) return 0; - if (xml_child_nr(x) != 1) + if (xml_child_nr_notype(x, CX_ATTR) != 1) return 0; - c = xml_child_i(x, 0); - return (xml_child_nr(c) == 0); + /* From here exactly one noattr child, get it */ + xc = NULL; + while ((xc = xml_child_each(x, xc, -1)) != NULL) + if (xml_type(xc) != CX_ATTR) + break; + if (xc == NULL) + return -1; /* n/a */ + return (xml_child_nr_notype(xc, CX_ATTR) == 0); } /*! Translate XML -> TEXT @@ -114,39 +121,42 @@ xml2txt(FILE *f, cxobj *x, int level) { - cxobj *xe = NULL; + cxobj *xc = NULL; int children=0; - char *term = NULL; int retval = -1; - xe = NULL; /* count children */ - while ((xe = xml_child_each(x, xe, -1)) != NULL) - children++; - if (!children){ - if (xml_type(x) == CX_BODY){ - term = xml_value(x); + xc = NULL; /* count children (elements and bodies, not attributes) */ + while ((xc = xml_child_each(x, xc, -1)) != NULL) + if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY) + children++; + if (!children){ /* If no children print line */ + switch (xml_type(x)){ + case CX_BODY: + fprintf(f, "%s;\n", xml_value(x)); + break; + case CX_ELMNT: + fprintf(f, "%*s;\n", 4*level, xml_name(x)); + break; + default: + break; } - else{ - fprintf(f, "%*s", 4*level, ""); - term = xml_name(x); - } - fprintf(f, "%s;\n", term); - retval = 0; - goto done; + goto ok; } fprintf(f, "%*s", 4*level, ""); fprintf(f, "%s ", xml_name(x)); if (!tleaf(x)) fprintf(f, "{\n"); - xe = NULL; - while ((xe = xml_child_each(x, xe, -1)) != NULL){ - if (xml2txt(f, xe, level+1) < 0) - break; + xc = NULL; + while ((xc = xml_child_each(x, xc, -1)) != NULL){ + if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY) + if (xml2txt(f, xc, level+1) < 0) + break; } if (!tleaf(x)) fprintf(f, "%*s}\n", 4*level, ""); + ok: retval = 0; - done: + // done: return retval; } @@ -172,7 +182,10 @@ xml2cli(FILE *f, int match; char *body; - ys = xml_spec(x); + if (xml_type(x)==CX_ATTR) + goto ok; + if ((ys = xml_spec(x)) == NULL) + goto ok; if (ys->ys_keyword == Y_LEAF || ys->ys_keyword == Y_LEAF_LIST){ if (prepend0) fprintf(f, "%s", prepend0); @@ -361,6 +374,37 @@ validate_identityref(cxobj *xt, goto done; } +/*! Given an XML node, return root node + * A root node is an ancestor xr of x with one or both of the following properties + * - its XML parent is NULL parent, + * - its associated yang specification's parent is a yang module. + * @param[in] x XML node + * @param[out] xr XML root + */ +int +xml_yang_root(cxobj *x, + cxobj **xr) +{ + int retval = -1; + cxobj *xp; + yang_stmt *y; + yang_stmt *yp; + + while ((xp = xml_parent(x)) != NULL){ + if ((y = xml_spec(x)) != NULL && + (yp = (yang_stmt*)y->ys_parent) != NULL) + /* Actually, maybe only the Y_MODULE clause is relevant */ + if (yp==NULL || + yp->ys_keyword == Y_MODULE || + yp->ys_keyword == Y_SUBMODULE) + break; /* x is the root */ + x = xp; + } + *xr = x; + retval = 0; + return retval; +} + /*! Validate an RPC node * @param[in] xt XML node to be validated * @retval 1 Validation OK @@ -408,7 +452,6 @@ xml_yang_validate_rpc(cxobj *xrpc, int retval = -1; yang_stmt *yn=NULL; /* rpc name */ cxobj *xn; /* rpc name */ - int ret; if (strcmp(xml_name(xrpc), "rpc")){ clicon_err(OE_XML, EINVAL, "Expected RPC"); @@ -417,16 +460,15 @@ xml_yang_validate_rpc(cxobj *xrpc, xn = NULL; /* xn is name of rpc, ie */ while ((xn = xml_child_each(xrpc, xn, CX_ELMNT)) != NULL) { - if ((yn = xml_spec(xn)) == NULL) - goto fail; - if ((ret = xml_yang_validate_all(xn, cbret)) < 0) - goto fail; - if (ret == 0) - goto fail; - if ((ret = xml_yang_validate_add(xn, cbret)) < 0) - goto fail; - if (ret == 0) + if ((yn = xml_spec(xn)) == NULL){ + if (netconf_unknown_element(cbret, "application", xml_name(xn), NULL) < 0) + goto done; goto fail; + } + if ((retval = xml_yang_validate_all(xn, cbret)) < 1) + goto done; /* error or validation fail */ + if ((retval = xml_yang_validate_add(xn, cbret)) < 1) + goto done; /* error or validation fail */ if (xml_apply0(xn, CX_ELMNT, xml_default, NULL) < 0) goto done; } @@ -443,9 +485,9 @@ xml_yang_validate_rpc(cxobj *xrpc, * 1. Check if mandatory leafs present as subs. * 2. Check leaf values, eg int ranges and string regexps. * @param[in] xt XML node to be validated - * @param[out] cbret Error buffer + * @param[out] cbret Error buffer (set w netconf error if retval == 0) * @retval 1 Validation OK - * @retval 0 Validation failed + * @retval 0 Validation failed (cbret set) * @retval -1 Error * @code * cxobj *x; @@ -456,6 +498,7 @@ xml_yang_validate_rpc(cxobj *xrpc, * fail; * @endcode * @see xml_yang_validate_all + * @see xml_yang_validate_rpc * @note Should need a variant accepting cxobj **xret */ int @@ -544,9 +587,9 @@ xml_yang_validate_add(cxobj *xt, /*! Validate a single XML node with yang specification for all (not only added) entries * 1. Check leafrefs. Eg you delete a leaf and a leafref references it. * @param[in] xt XML node to be validated - * @param[out] cbret Error buffer + * @param[out] cbret Error buffer (set w netconf error if retval == 0) * @retval 1 Validation OK - * @retval 0 Validation failed + * @retval 0 Validation failed (cbret set) * @retval -1 Error * @code * cxobj *x; @@ -680,6 +723,8 @@ xml_yang_validate_all(cxobj *xt, goto done; } +/*! Translate a single xml node to a cligen variable vector. Note not recursive + */ int xml_yang_validate_all_top(cxobj *xt, cbuf *cbret) @@ -695,6 +740,47 @@ xml_yang_validate_all_top(cxobj *xt, return 1; } +/*! Given XML node x, find yang spec in _any_ module matching name + * This is non-struct namespace semantics (not correct) but necessary + * in historic Clixon code. + * Also, add a proper default namespaces statement (xmlns="uri") in x + * @param[in] x XML node (find yang statement on this one) + * @param[in] yspec Top-level yang spec + * @param[out] y Yang stmt associated to x. NULL i not found + * @retval 0 OK + * @see CLICON_XML_NS_STRICT clixon config option + */ +int +xml_yang_find_non_strict(cxobj *x, + yang_spec *yspec, + yang_stmt **yp) +{ + int retval = -1; + char *name; + yang_stmt *ymod; + int i; + yang_stmt *y=NULL; + char *ns; + + name = xml_name(x); + for (i=0; iyp_len; i++){ + ymod = yspec->yp_stmt[i]; + if ((y = yang_find_schemanode((yang_node*)ymod, name)) != NULL) + break; + } + if (y){ + *yp = y; + if ((ns = yang_find_mynamespace(ymod)) != NULL){ + if (xml_find_type_value(x, NULL, "xmlns", CX_ATTR) == NULL) + if (xmlns_set(x, NULL, ns) < 0) + goto done; + } + } + retval = 0; + done: + return retval; +} + /*! Translate a single xml node to a cligen variable vector. Note not recursive * @param[in] xt XML tree containing one top node * @param[in] ys Yang spec containing type specification of top-node of xt @@ -861,28 +947,6 @@ cvec2xml_1(cvec *cvv, return retval; } - -/*! Find next yang node, either start from yang_spec or some yang-node - * @param[in] y Node spec or sny yang-node - * @param[in] name Name of childnode to find - * @retval ys yang statement - * @retval NULL Error: no node found - */ -static yang_stmt * -yang_next(yang_node *y, - char *name) -{ - yang_stmt *ys; - - if (y->yn_keyword == Y_SPEC) - ys = yang_find_topnode((yang_spec*)y, name, YC_DATANODE); - else - ys = yang_find_datanode(y, name); - if (ys == NULL) - clicon_err(OE_UNIX, errno, "No yang node found: %s", name); - return ys; -} - /*! Recursive help function to compute differences between two xml trees * @param[in] x1 First XML tree * @param[in] x2 Second XML tree @@ -920,9 +984,12 @@ xml_diff1(yang_stmt *ys, */ x1c = NULL; while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL){ - if ((yc = yang_next((yang_node*)ys, xml_name(x1c))) == NULL) + if ((yc = xml_spec(x1c)) == NULL){ + clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x1c)); goto done; - if (match_base_child(x2, x1c, &x2c, yc) < 0) + } + /* XXX xml_child_sort is global */ + if (match_base_child(x2, x1c, &x2c, xml_child_sort, yc) < 0) goto done; if (x2c == NULL){ if (cxvec_append(x1c, x1vec, x1veclen) < 0) @@ -954,9 +1021,12 @@ xml_diff1(yang_stmt *ys, */ x2c = NULL; while ((x2c = xml_child_each(x2, x2c, CX_ELMNT)) != NULL){ - if ((yc = yang_next((yang_node*)ys, xml_name(x2c))) == NULL) + if ((yc = xml_spec(x2c)) == NULL){ + clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x2c)); goto done; - if (match_base_child(x1, x2c, &x1c, yc) < 0) + } + /* XXX xml_child_sort is global */ + if (match_base_child(x1, x2c, &x1c, xml_child_sort, yc) < 0) goto done; if (x1c == NULL) if (cxvec_append(x2c, x2vec, x2veclen) < 0) @@ -1025,10 +1095,11 @@ xml_diff(yang_spec *yspec, * Recursively construct it to the top. * Example: * yang: container a -> list b -> key c -> leaf d - * xpath: /a/b/%s/d + * xpath: /modname:a/b/%s/d * @param[in] ys Yang statement * @param[in] inclkey If set include key leaf (eg last leaf d in ex) * @param[out] cb api_path_fmt, + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 */ static int yang2api_path_fmt_1(yang_stmt *ys, @@ -1040,28 +1111,35 @@ yang2api_path_fmt_1(yang_stmt *ys, cvec *cvk = NULL; /* vector of index keys */ int retval = -1; - yp = ys->ys_parent; - if (yp != NULL && + if ((yp = ys->ys_parent) == NULL){ + clicon_err(OE_YANG, EINVAL, "yang expected parent %s", ys->ys_argument); + goto done; + } + if (yp != NULL && /* XXX rm */ yp->yn_keyword != Y_MODULE && yp->yn_keyword != Y_SUBMODULE){ - if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) + if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) /* recursive call */ goto done; + cprintf(cb, "/"); } + else /* top symbol - mark with name prefix */ + cprintf(cb, "/%s:", yp->yn_argument); + if (inclkey){ if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE) - cprintf(cb, "/%s", ys->ys_argument); + cprintf(cb, "%s", ys->ys_argument); } else{ #if 1 if (ys->ys_keyword == Y_LEAF && yp && yp->yn_keyword == Y_LIST){ if (yang_key_match(yp, ys->ys_argument) == 0) - cprintf(cb, "/%s", ys->ys_argument); /* Not if leaf and key */ + cprintf(cb, "%s", ys->ys_argument); /* Not if leaf and key */ } else #endif if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE) - cprintf(cb, "/%s", ys->ys_argument); + cprintf(cb, "%s", ys->ys_argument); } switch (ys->ys_keyword){ @@ -1095,6 +1173,7 @@ yang2api_path_fmt_1(yang_stmt *ys, * @param[in] ys Yang statement * @param[in] inclkey If set include key leaf (eg last leaf d in ex) * @param[out] api_path_fmt XML api path. Needs to be freed after use. + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 */ int yang2api_path_fmt(yang_stmt *ys, @@ -1148,6 +1227,8 @@ yang2api_path_fmt(yang_stmt *ys, * api_path_fmt: /subif-entry=%s,%s/subid * cvv: foo * api_path: /subif-entry=foo/subid + * + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 */ int api_path_fmt2api_path(char *api_path_fmt, @@ -1211,7 +1292,6 @@ api_path_fmt2api_path(char *api_path_fmt, return retval; } - /*! Transform an xml key format and a vector of values to an XML path * Used to input xmldb_get() or xmldb_get_vec * @param[in] api_path_fmt XML key format @@ -1230,6 +1310,8 @@ api_path_fmt2api_path(char *api_path_fmt, * api_path_fmt: /subif-entry=%s,%s/subid * cvv: foo * xpath: /subif-entry[if-name=foo]/subid" + * + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 */ int api_path_fmt2xpath(char *api_path_fmt, @@ -1470,6 +1552,9 @@ xml_default(cxobj *xt, /*! Order XML children according to YANG * @param[in] xt XML top of tree + * @param[in] arg Dummy (so it can be called from xml_apply) + * @see xml_sort XXX: how do they relate? + * Called from text_get *only* */ int xml_order(cxobj *xt, @@ -1603,9 +1688,7 @@ xml_spec_populate_rpc(clicon_handle h, yang_stmt *y=NULL; /* yang node */ yang_stmt *ymod=NULL; /* yang module */ yang_stmt *yi = NULL; /* input */ - // yang_stmt *ya = NULL; /* arg */ cxobj *x; - // cxobj *xi; int i; if ((strcmp(xml_name(xrpc), "rpc"))!=0){ @@ -1621,7 +1704,7 @@ xml_spec_populate_rpc(clicon_handle h, /* Loose semantics: loop through all modules to find the node */ if (y == NULL && - clicon_option_bool(h, "CLICON_XML_NS_ITERATE")){ + !clicon_option_bool(h, "CLICON_XML_NS_STRICT")){ for (i=0; iyp_len; i++){ ymod = yspec->yp_stmt[i]; if ((y = yang_find((yang_node*)ymod, Y_RPC, xml_name(x))) != NULL) @@ -1633,16 +1716,8 @@ xml_spec_populate_rpc(clicon_handle h, if ((yi = yang_find((yang_node*)y, Y_INPUT, NULL)) != NULL){ /* kludge rpc -> input */ xml_spec_set(x, yi); -#if 1 if (xml_apply(x, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; -#else - xi = NULL; - while ((xi = xml_child_each(x, xi, CX_ELMNT)) != NULL) { - if ((ya = yang_find_datanode((yang_node*)yi, xml_name(xi))) != NULL) - xml_spec_set(xi, ya); - } -#endif } } } @@ -1651,13 +1726,14 @@ xml_spec_populate_rpc(clicon_handle h, return retval; } + /*! Add yang specification backpointer to XML node * @param[in] xt XML tree node * @param[in] arg Yang spec * @note This may be unnecessary if yspec is set on creation * @note For subs to anyxml nodes will not have spec set * @note No validation is done,... XXX - * @note relies on kludge _CLICON_XML_NS_ITERATE + * @note relies on kludge _CLICON_XML_NS_STRICT * @code * xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) * @endcode @@ -1674,7 +1750,6 @@ xml_spec_populate(cxobj *x, yang_stmt *ymod; /* yang module */ cxobj *xp; /* xml parent */ char *name; - int i; yspec = (yang_spec*)arg; if (xml_spec(x)) @@ -1688,15 +1763,12 @@ xml_spec_populate(cxobj *x, goto done; if (ymod != NULL) y = yang_find_datanode((yang_node*)ymod, name); - /* Loose semantics: loop through all modules to find the node - * XXX clicon_option_bool(h, "CLICON_XML_NS_ITERATE") + /* Non-strict semantics: loop through all modules to find the node + * XXX clicon_option_bool(h, "CLICON_XML_NS_STRICT") */ - if (y == NULL && _CLICON_XML_NS_ITERATE){ - for (i=0; iyp_len; i++){ - ymod = yspec->yp_stmt[i]; - if ((y = yang_find_datanode((yang_node*)ymod, name)) != NULL) - break; - } + if (y == NULL && !_CLICON_XML_NS_STRICT){ + if (xml_yang_find_non_strict(x, yspec, &y) < 0) + goto done; } } if (y) @@ -1713,15 +1785,19 @@ xml_spec_populate(cxobj *x, return retval; } - /*! Translate from restconf api-path in cvv form to xml xpath * eg a/b=c -> a/[b=c] - * @param[in] yspec Yang spec - * @param[in] pcvec api-path as cvec - * @param[in] pi Offset of cvec, where api-path starts - * @param[out] path The xpath as cbuf variable string, must be initializeed - * The api-path has some wierd encoding, use api_path2xpath() if you are - * confused. + * eg example:a/b -> ex:a/b + * @param[in] yspec Yang spec + * @param[in] cvv api-path as cvec + * @param[in] offset Offset of cvec, where api-path starts + * @param[out] xpath The xpath as cbuf variable string, must be initializeed + * @retval 1 OK + * @retval 0 Invalid api_path or associated XML, clicon_err called + * @retval -1 Fatal error, clicon_err called + * + * @note both retval 0 and -1 set clicon_err, but the later is fatal + * @note Not proper namespace translation from api-path 2 xpath * It works like this: * Assume origin incoming path is * "www.foo.com/restconf/a/b=c", pi is 2 and pcvec is: @@ -1729,46 +1805,63 @@ xml_spec_populate(cxobj *x, * which means the api-path is ["a" "b=c"] corresponding to "a/b=c" * @code * cbuf *xpath = cbuf_new(); - * if (api_path2xpath_cvv(yspec, cvv, i, xpath) + * cvec *cvv = NULL; + * if (str2cvec("www.foo.com/restconf/a/b=c", '/', '=', &cvv) < 0) * err; + * if ((ret = api_path2xpath(yspec, cvv, 0, cxpath)) < 0) + * err; + * if (ret == 0){ + * ... access error string in clicon_err_reason + * clicon_err_reset(); + * return; + * } * ... access xpath as cbuf_get(xpath) * cbuf_free(xpath) * @endcode - * @see api_path2xpath for string api-path argument + * @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 + * @see api_path2xml For api-path to xml tree */ int -api_path2xpath_cvv(yang_spec *yspec, - cvec *cvv, - int offset, - cbuf *xpath) +api_path2xpath(yang_spec *yspec, + cvec *cvv, + int offset, + cbuf *xpath) { int retval = -1; int i; cg_var *cv; - char *name; + char *nodeid; + char *prefix = NULL; + char *name = NULL; cvec *cvk = NULL; /* vector of index keys */ yang_stmt *y = NULL; + yang_stmt *ymod; char *val; char *v; cg_var *cvi; for (i=offset; iyn_keyword == Y_SPEC){ /* top-node */ - y = yang_find_topnode((yang_spec*)y0, name, nodeclass); - } - else { - y = (nodeclass==YC_SCHEMANODE)?yang_find_schemanode((yang_node*)y0, name): - yang_find_datanode((yang_node*)y0, name); - } - if (y == NULL){ - clicon_err(OE_YANG, errno, "No yang node found: %s", name); + /* Split into prefix and localname */ + if (nodeid_split(nodeid, &prefix, &name) < 0) goto done; + if (y0->yn_keyword == Y_SPEC){ /* top-node */ + if (prefix == NULL){ + clicon_err(OE_XML, EINVAL, "api-path element '%s', expected prefix:name", nodeid); + goto fail; + } + if ((ymod = yang_find_module_by_name((yang_spec*)y0, prefix)) == NULL){ + clicon_err(OE_YANG, EINVAL, "api-path element prefix: '%s', no such yang module", prefix); + goto fail; + } + namespace = yang_find_mynamespace(ymod); + y0 = (yang_node*)ymod; + + } + y = (nodeclass==YC_SCHEMANODE)? + yang_find_schemanode((yang_node*)y0, name): + yang_find_datanode((yang_node*)y0, name); + if (y == NULL){ + clicon_err(OE_YANG, EINVAL, "api-path name: '%s', no such yang element", name); + goto fail; } switch (y->ys_keyword){ case Y_LEAF_LIST: if (0 && restval==NULL){ - clicon_err(OE_XML, 0, "malformed key, expected '='"); + clicon_err(OE_XML, 0, "malformed key, expected '=restval'"); goto done; } if ((x = xml_new(y->ys_argument, x0, y)) == NULL) @@ -1924,21 +2014,21 @@ api_path2xml_vec(char **vec, } if (restval==NULL){ // XXX patch to allow for lists without restval to be backward compat - // clicon_err(OE_XML, 0, "malformed key, expected '='"); + // clicon_err(OE_XML, 0, "malformed key, expected '=restval'"); // goto done; } else{ if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL) goto done; if (nvalvec > cvec_len(cvk)){ - clicon_err(OE_XML, errno, "List %s key length mismatch", name); - goto done; + clicon_err(OE_XML, EINVAL, "List key %s length mismatch", name); + goto fail; } } cvi = NULL; /* create list object */ if ((x = xml_new(name, x0, y)) == NULL) - goto done; + goto done; xml_type_set(x, CX_ELMNT); j = 0; /* Create keys */ @@ -1957,31 +2047,47 @@ api_path2xml_vec(char **vec, break; default: /* eg Y_CONTAINER, Y_LEAF */ if ((x = xml_new(name, x0, y)) == NULL) - goto done; + goto done; xml_type_set(x, CX_ELMNT); break; } - if (api_path2xml_vec(vec+1, nvec-1, - x, (yang_node*)y, - nodeclass, - xpathp, ypathp) < 0) + if (x && namespace){ + if (xmlns_set(x, NULL, namespace) < 0) + goto done; + } + if ((retval = api_path2xml_vec(vec+1, nvec-1, + x, (yang_node*)y, + nodeclass, + xpathp, ypathp)) < 1) goto done; - retval = 0; + ok: + retval = 1; /* OK */ done: + if (prefix) + free(prefix); + if (name) + free(name); if (restval) free(restval); if (valvec) free(valvec); return retval; + fail: + retval = 0; /* invalid api-path or XML */ + goto done; } /*! Create xml tree from api-path - * @param[in] api_path API-path as defined in RFC 8040 + * @param[in] api_path (Absolute) API-path as defined in RFC 8040 * @param[in] yspec Yang spec * @param[in,out] xtop Incoming XML tree * @param[in] nodeclass Set to schema nodes, data nodes, etc * @param[out] xbotp Resulting xml tree (end of xpath) * @param[out] ybotp Yang spec matching xbotp + * @retval 1 OK + * @retval 0 Invalid api_path or associated XML, clicon_err called + * @retval -1 Fatal error, clicon_err called + * @note both retval 0 and -1 set clicon_err, but the later is fatal * @example * api_path: /subif-entry=foo/subid * xtop[in] @@ -1990,7 +2096,8 @@ api_path2xml_vec(char **vec, * * xbotp: * ybotp: Y_LEAF subid - * @see api_path2xml_vec + * @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 + * @see api_path2xpath For api-path to xml xpath translation */ int api_path2xml(char *api_path, @@ -2003,10 +2110,13 @@ api_path2xml(char *api_path, int retval = -1; char **vec = NULL; int nvec; + cxobj *xroot; + clicon_debug(1, "%s", __FUNCTION__); if (*api_path!='/'){ - clicon_log(LOG_WARNING, "Invalid key: %s (must start with '/')", api_path); - goto ok; + clicon_err(OE_XML, EINVAL, "Invalid api-path: %s (must start with '/')", + api_path); + goto fail; } if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL) goto done; @@ -2014,19 +2124,70 @@ api_path2xml(char *api_path, if (nvec > 1 && !strlen(vec[nvec-1])) nvec--; if (nvec < 1){ - clicon_err(OE_XML, 0, "Malformed key: %s", api_path); - goto done; + clicon_err(OE_XML, EINVAL, "Malformed api-path: %s", api_path); + goto fail; } nvec--; /* NULL-terminated */ - if (api_path2xml_vec(vec+1, nvec, - xtop, (yang_node*)yspec, nodeclass, - xbotp, ybotp) < 0) + if ((retval = api_path2xml_vec(vec+1, nvec, + xtop, (yang_node*)yspec, nodeclass, + xbotp, ybotp)) < 1) + goto done; + xml_yang_root(*xbotp, &xroot); + if (xmlns_assign(xroot) < 0) + goto done; + // ok: + retval = 1; + done: + if (vec) + free(vec); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Check if the module tree x is in is assigned right XML namespace, assign if not + * @param[in] x XML node + *(0. You should probably find the XML root and apply this function to that.) + * 1. Check which namespace x should have (via yang). This is correct namespace. + * 2. Check which namespace x has via its XML tree + * 3. If equal, OK + * 4. Assign the correct namespace to the XML node + * Assign default namespace to x. Create an "xmlns" + * @code + * xml_yang_root(x, &xroot); + * xmlns_assign(xroot); + * @endcode + */ +int +xmlns_assign(cxobj *x) +{ + int retval = -1; + yang_stmt *y; + char *ns_correct; /* correct uri */ + char *ns_xml; /* may be null or incorrect */ + + if ((y = xml_spec(x)) == NULL){ + clicon_err(OE_YANG, ENOENT, "XML %s does not have yang spec", xml_name(x)); + goto done; + } + /* 1. Check which namespace x should have (via yang). This is correct namespace. */ + if ((ns_correct = yang_find_mynamespace(y)) == NULL){ + clicon_err(OE_YANG, ENOENT, "yang node %s does not have namespace", y->ys_argument); + goto done; + } + /* 2. Check which namespace x has via its XML tree */ + if (xml2ns(x, NULL, &ns_xml) < 0) + goto done; + /* 3. If equal, OK, 4. Else, find root of XML tree */ + if (ns_xml && strcmp(ns_xml, ns_correct)==0) + goto ok; + /* 4. Assign the correct namespace */ + if (xmlns_set(x, NULL, ns_correct) < 0) goto done; ok: retval = 0; done: - if (vec) - free(vec); return retval; } @@ -2053,6 +2214,8 @@ xml_merge1(cxobj *x0, cxobj *x0c; /* base child */ cxobj *x0b; /* base body */ cxobj *x1c; /* mod child */ + cxobj *x0a; /* x0 xmlns attribute */ + cxobj *x1a; /* x1 xmlns attribute */ char *x1bstr; /* mod body string */ yang_stmt *yc; /* yang child */ cbuf *cbr = NULL; /* Reason buffer */ @@ -2109,7 +2272,7 @@ xml_merge1(cxobj *x0, } /* See if there is a corresponding node in the base tree */ x0c = NULL; - if (yc && match_base_child(x0, x1c, &x0c, yc) < 0) + if (yc && match_base_child(x0, x1c, &x0c, xml_child_sort, yc) < 0) goto done; if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0) goto done; @@ -2117,6 +2280,17 @@ xml_merge1(cxobj *x0, goto ok; } } /* else Y_CONTAINER */ + assert(x0); + /* Copy xmlns attributes (if it does not already exist) */ + if ((x1a = xml_find_type(x1, NULL, "xmlns", CX_ATTR)) != NULL) + if (xml_find_type(x0, NULL, "xmlns", CX_ATTR)==NULL){ + if ((x0a = xml_dup(x1a)) == NULL) + goto done; + if (xml_addsub(x0, x0a) < 0) + goto done; + } + + ok: retval = 0; done: @@ -2147,20 +2321,31 @@ xml_merge(cxobj *x0, cxobj *x0c; /* base child */ cxobj *x1c; /* mod child */ yang_stmt *yc; + yang_stmt *ymod; cbuf *cbr = NULL; /* Reason buffer */ /* Loop through children of the modification tree */ x1c = NULL; while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); + if ((ys_module_by_xml(yspec, x1c, &ymod)) < 0) + goto done; + if (ymod == NULL){ + if (reason && + (*reason = strdup("No namespace in XML tree found")) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + goto ok; + } /* Get yang spec of the child */ - if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == NULL){ + if ((yc = yang_find_datanode((yang_node*)ymod, x1cname)) == NULL){ if (reason){ if ((cbr = cbuf_new()) == NULL){ clicon_err(OE_XML, errno, "cbuf_new"); goto done; } - cprintf(cbr, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?", xml_name(x1), x1cname); + cprintf(cbr, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?)", xml_name(x1), x1cname); if ((*reason = strdup(cbuf_get(cbr))) == NULL){ clicon_err(OE_UNIX, errno, "strdup"); goto done; @@ -2169,13 +2354,14 @@ xml_merge(cxobj *x0, break; } /* See if there is a corresponding node in the base tree */ - if (match_base_child(x0, x1c, &x0c, yc) < 0) + if (match_base_child(x0, x1c, &x0c, xml_child_sort, yc) < 0) goto done; if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0) goto done; if (*reason != NULL) break; } + ok: retval = 0; /* OK */ done: if (cbr) diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y index 7600d249..d6a1200f 100644 --- a/lib/src/clixon_xml_parse.y +++ b/lib/src/clixon_xml_parse.y @@ -130,6 +130,7 @@ xml_parse_version(struct xml_parse_yacc_arg *ya, * @param[in] ya XML parser yacc handler struct * @param[in] prefix Prefix, namespace, or NULL * @param[in] localpart Name + * @note the call to xml_child_spec() may not have xmlns attribute read yet XXX */ static int xml_parse_unprefixed_name(struct xml_parse_yacc_arg *ya, @@ -138,12 +139,14 @@ xml_parse_unprefixed_name(struct xml_parse_yacc_arg *ya, int retval = -1; cxobj *x; yang_stmt *y = NULL; /* yang node */ - cxobj *xp; /* xml parent */ + cxobj *xp; /* xml parent */ xp = ya->ya_xparent; - if (xml_child_spec(name, xp, ya->ya_yspec, &y) < 0) + if ((x = xml_new(name, xp, NULL)) == NULL) goto done; - if ((x = xml_new(name, xp, y)) == NULL) + if (xml_child_spec(x, xp, ya->ya_yspec, &y) < 0) + goto done; + if (y && xml_spec_set(x, y) < 0) goto done; ya->ya_xelement = x; retval = 0; @@ -168,9 +171,11 @@ xml_parse_prefixed_name(struct xml_parse_yacc_arg *ya, cxobj *xp; /* xml parent */ xp = ya->ya_xparent; - if (xml_child_spec(name, xp, ya->ya_yspec, &y) < 0) + if ((x = xml_new(name, xp, NULL)) == NULL) goto done; - if ((x = xml_new(name, xp, y)) == NULL) + if (xml_child_spec(x, xp, ya->ya_yspec, &y) < 0) + goto done; + if (y && xml_spec_set(x, y) < 0) goto done; if (xml_prefix_set(x, prefix) < 0) goto done; @@ -313,19 +318,15 @@ xml_parse_attr(struct xml_parse_yacc_arg *ya, char *attval) { int retval = -1; - cxobj *xa; + cxobj *xa = NULL; -#ifdef ENABLE_XMLNS - if (prefix && strcmp(prefix,"xmlns")==0) - fprintf(stderr, "PrefixedAttName NCNAME:%s = %s\n", name, attval); - if (prefix==NULL && strcmp(name,"xmlns")==0) - fprintf(stderr, "DefaultAttName = %s\n", attval); -#endif /* notyet */ - if ((xa = xml_new(name, ya->ya_xelement, NULL)) == NULL) - goto done; - xml_type_set(xa, CX_ATTR); - if (prefix && xml_prefix_set(xa, prefix) < 0) - goto done; + if ((xa = xml_find_type(ya->ya_xelement, prefix, name, CX_ATTR)) == NULL){ + if ((xa = xml_new(name, ya->ya_xelement, NULL)) == NULL) + goto done; + xml_type_set(xa, CX_ATTR); + if (prefix && xml_prefix_set(xa, prefix) < 0) + goto done; + } if (xml_value_set(xa, attval) < 0) goto done; retval = 0; diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index befb5bcb..265d82a5 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -61,6 +61,8 @@ #include "clixon_handle.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_options.h" +#include "clixon_xml_map.h" #include "clixon_xml_sort.h" /* @@ -68,7 +70,9 @@ */ /* Sort and binary search of XML children - * Experimental + * XXX kludge since low-level functions xml_merge/xml_diff calls + * match_base_child without handle + * @see clicon_xml_sort */ int xml_child_sort = 1; @@ -83,7 +87,7 @@ int xml_child_sort = 1; * xmlns and xmlns:ns are used. */ int -xml_child_spec(char *name, +xml_child_spec(cxobj *x, cxobj *xp, yang_spec *yspec, yang_stmt **yresult) @@ -93,8 +97,9 @@ xml_child_spec(char *name, yang_stmt *yparent; /* parent yang */ yang_stmt *ymod = NULL; yang_stmt *yi; - int i; + char *name; + name = xml_name(x); if (xp && (yparent = xml_spec(xp)) != NULL){ if (yparent->ys_keyword == Y_RPC){ if ((yi = yang_find((yang_node*)yparent, Y_INPUT, NULL)) != NULL) @@ -108,12 +113,9 @@ xml_child_spec(char *name, goto done; if (ymod != NULL) y = yang_find_schemanode((yang_node*)ymod, name); - if (y == NULL && _CLICON_XML_NS_ITERATE){ - for (i=0; iyp_len; i++){ - ymod = yspec->yp_stmt[i]; - if ((y = yang_find_schemanode((yang_node*)ymod, name)) != NULL) - break; - } + if (y == NULL && !_CLICON_XML_NS_STRICT){ + if (xml_yang_find_non_strict(x, yspec, &y) < 0) /* schemanode */ + goto done; } } else @@ -265,6 +267,7 @@ xml_cmp1(cxobj *x, * Assume populated by yang spec. * @param[in] x0 XML node * @param[in] arg Dummy so it can be called by xml_apply() + * @see xml_order XXX: how do they relate? */ int xml_sort(cxobj *x, @@ -560,9 +563,10 @@ xml_sort_verify(cxobj *x0, } /*! Given child tree x1c, find matching child in base tree x0 and return as x0cp - * param[in] x0 Base tree node - * param[in] x1c Modification tree child - * param[in] yc Yang spec of tree child + * param[in] x0 Base tree node + * param[in] x1c Modification tree child + * param[in] yc Yang spec of tree child + * param[in] xml_sort Value of CLICON_XML_SORT option * param[out] x0cp Matching base tree child (if any) * @note XXX: room for optimization? on 1K calls we have 1M body calls and 500K xml_child_each/cvec_each calls. @@ -575,6 +579,7 @@ int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, + int xml_sort, yang_stmt *yc) { int retval = -1; @@ -632,7 +637,7 @@ match_base_child(cxobj *x0, break; } /* Get match. Sorting mode(optimized) or not?*/ - if (xml_child_sort==0) + if (xml_sort==0) *x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); else{ if (xml_child_nr(x0)==0 || xml_spec(xml_child_i(x0,0))!=NULL){ @@ -640,12 +645,6 @@ match_base_child(cxobj *x0, *x0cp = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval); } else{ -#if 1 /* This is just a warning, but a catcher for when xml tree is not - populated with yang spec. If you see this, a previous invacation of, - for example xml_spec_populate() may be missing - */ - clicon_log(LOG_WARNING, "%s No yspec", __FUNCTION__); -#endif *x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); } } diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 68c9ce9f..2298235a 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -469,94 +469,6 @@ yang_match(yang_node *yn, } return match; } -#ifdef NOTYET -/*! Prototype more generic than yang_find_datanode and yang_find_schemanode - */ -yang_stmt * -yang_find_class(yang_node *yn, - char *argument, - yang_class class) -{ - yang_stmt *ys = NULL; - yang_stmt *yc = NULL; - yang_stmt *ysmatch = NULL; - int i, j; - int ok; - - for (i=0; iyn_len; i++){ - ys = yn->yn_stmt[i]; - switch(class){ - case YC_NONE: - ok = 1; - break; - case YC_DATANODE: - ok = yang_datanode(ys); - break; - case YC_DATADEFINITION: - ok = yang_datadefinition(ys); - break; - case YC_SCHEMANODE: - ok = yang_schemanode(ys); - break; - } - if (!ok) - continue; - switch(class){ - case YC_NONE: - if (argument == NULL) - ysmatch = ys; - else - if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) - ysmatch = ys; - if (ysmatch) - goto match; - break; - case YC_DATANODE: - case YC_DATADEFINITION: - if (argument == NULL) - ysmatch = ys; - else - if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) - ysmatch = ys; - if (ysmatch) - goto match; - break; - case YC_SCHEMANODE: - if (ys->ys_keyword == Y_CHOICE){ /* Look for its children */ - for (j=0; jys_len; j++){ - yc = ys->ys_stmt[j]; - if (yc->ys_keyword == Y_CASE) /* Look for its children */ - ysmatch = yang_find_class((yang_node*)yc, argument, class); - else{ - if (yang_schemanode(yc)){ - if (argument == NULL) - ysmatch = yc; - else - if (yc->ys_argument && strcmp(argument, yc->ys_argument) == 0) - ysmatch = yc; - } - } - if (ysmatch) - goto match; - } - } /* Y_CHOICE */ - else{ - if (argument == NULL) - ysmatch = ys; - else - if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) - ysmatch = ys; - if (ysmatch) - goto match; - - } - break; - } /* switch */ - } /* for */ - match: - return ysmatch; -} -#endif /* NOTYET */ /*! Find child data node with matching argument (container, leaf, etc) * @@ -658,69 +570,6 @@ yang_find_schemanode(yang_node *yn, return ysmatch; } -/*! Find first matching data node in all modules in a yang spec (prefixes) - * - * @param[in] ysp Yang specification - * @param[in] nodeid Name of node. If NULL match first - * @param[in] class See yang_class for class of yang nodes - * A yang specification has modules as children which in turn can have - * syntax-nodes as children. This function goes through all the modules to - * look for nodes. Note that if a child to a module is a choice, - * the search is made recursively made to the choice's children. - * @note works for import prefix, but not work for generic XML parsing where - * xmlns and xmlns:ns are used. - * @see yang_find_top_ns - */ -yang_stmt * -yang_find_topnode(yang_spec *ysp, - char *nodeid, - yang_class class) -{ - yang_stmt *ymod = NULL; /* module */ - yang_stmt *yres = NULL; /* result */ - char *prefix = NULL; - char *id = NULL; - int i; - - if (yang_nodeid_split(nodeid, &prefix, &id) < 0) - goto done; - if (prefix){ - if ((ymod = yang_find((yang_node*)ysp, Y_MODULE, prefix)) != NULL || - (ymod = yang_find((yang_node*)ysp, Y_SUBMODULE, prefix)) != NULL){ - if ((yres = yang_find((yang_node*)ymod, 0, id)) != NULL) - goto ok; - goto done; - } - } - else /* No prefix given - loop through and find first */ - for (i=0; iyp_len; i++){ - ymod = ysp->yp_stmt[i]; - switch (class){ - case YC_NONE: - if ((yres = yang_find((yang_node*)ymod, 0, id)) != NULL) - goto ok; - break; - case YC_DATANODE: - if ((yres = yang_find_datanode((yang_node*)ymod, id)) != NULL) - goto ok; - break; - case YC_SCHEMANODE: - if ((yres = yang_find_schemanode((yang_node*)ymod, id)) != NULL) - goto ok; - break; - case YC_DATADEFINITION: - break; /* nyi */ - } - } - ok: - done: - if (prefix) - free(prefix); - if (id) - free(id); - return yres; -} - /*! Given a yang statement, find the prefix associated to this module * @param[in] ys Yang statement in module tree (or module itself) * @retval NULL Not found @@ -775,7 +624,6 @@ yang_find_mynamespace(yang_stmt *ys) return namespace; } - /*! Find matching y in yp:s children, return 0 and index or -1 if not found. * @retval 0 not found * @retval 1 found @@ -972,60 +820,15 @@ yarg_prefix(yang_stmt *ys) return prefix; } -/*! Split yang node identifier into prefix and identifer. - * @param[in] node-id - * @param[out] prefix Malloced string. May be NULL. - * @param[out] id Malloced identifier. - * @retval 0 OK - * @retval -1 Error - * @code - * char *prefix = NULL; - * char *id = NULL; - * if (yang_nodeid_split(nodeid, &prefix, &id) < 0) - * goto done; - * if (prefix) - * free(prefix); - * if (id) - * free(id); - * @note caller need to free id and prefix after use - */ -int -yang_nodeid_split(char *nodeid, - char **prefix, - char **id) -{ - int retval = -1; - char *str; - - if ((str = strchr(nodeid, ':')) == NULL){ - if ((*id = strdup(nodeid)) == NULL){ - clicon_err(OE_YANG, errno, "strdup"); - goto done; - } - } - else{ - if ((*prefix = strdup(nodeid)) == NULL){ - clicon_err(OE_YANG, errno, "strdup"); - goto done; - } - (*prefix)[str-nodeid] = '\0'; - str++; - if ((*id = strdup(str)) == NULL){ - clicon_err(OE_YANG, errno, "strdup"); - goto done; - } - } - retval = 0; - done: - return retval; -} - -/*! Given a yang statement and a prefix, return yang module to that prefix +/*! Given a yang statement and a prefix, return yang module to that relative prefix * Note, not the other module but the proxy import statement only * @param[in] ys A yang statement * @param[in] prefix prefix * @retval ymod Yang module statement if found * @retval NULL not found + * @node Prefixes are relative to the module they are defined + * @see yang_find_module_by_name + * @see yang_find_module_by_namespace */ yang_stmt * yang_find_module_by_prefix(yang_stmt *ys, @@ -1081,12 +884,14 @@ yang_find_module_by_prefix(yang_stmt *ys, return ymod; } -/*! Given a yang statement and a namespace, return yang module +/*! Given a yang spec and a namespace, return yang module * * @param[in] yspec A yang specification * @param[in] namespace namespace * @retval ymod Yang module statement if found * @retval NULL not found + * @see yang_find_module_by_name + * @see yang_find_module_by_prefix module-specific prefix */ yang_stmt * yang_find_module_by_namespace(yang_spec *yspec, @@ -1104,6 +909,28 @@ yang_find_module_by_namespace(yang_spec *yspec, return ymod; } +/*! Given a yang spec and a module name, return yang module + * + * @param[in] yspec A yang specification + * @param[in] name Name of module + * @retval ymod Yang module statement if found + * @retval NULL not found + * @see yang_find_module_by_namespace + * @see yang_find_module_by_prefix module-specific prefix + */ +yang_stmt * +yang_find_module_by_name(yang_spec *yspec, + char *name) +{ + yang_stmt *ymod = NULL; + + while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) + if ((ymod->ys_keyword == Y_MODULE || ymod->ys_keyword == Y_SUBMODULE) && + strcmp(ymod->ys_argument, name)==0) + return ymod; + return NULL; +} + /*! string is quoted if it contains space or tab, needs double '' */ static int inline quotedstring(char *s) @@ -1541,7 +1368,7 @@ ys_populate_feature(clicon_handle h, if (strcmp(xml_name(xc), "CLICON_FEATURE") != 0) continue; /* get m and f from configuration feature rules */ - if (yang_nodeid_split(xml_body(xc), &m, &f) < 0) + if (nodeid_split(xml_body(xc), &m, &f) < 0) goto done; if (m && f && (strcmp(m,"*")==0 || @@ -1558,7 +1385,8 @@ ys_populate_feature(clicon_handle h, } cv_name_set(cv, feature); cv_bool_set(cv, found); - clicon_debug(1, "%s %s:%s %d", __FUNCTION__, module, feature, found); + if (found) + clicon_debug(1, "%s %s:%s", __FUNCTION__, module, feature); ys->ys_cv = cv; ok: retval = 0; @@ -1936,7 +1764,7 @@ yang_parse_str(char *str, } /*! Parse yang spec from an open file descriptor - * @param[in] fd File descriptor containing the YANG file as ASCII characters + * @param[in] fd File descriptor containing the YANG file as ASCII characters * @param[in] name For debug, eg filename * @param[in] ysp Yang specification. Should have been created by caller using yspec_new * @retval ymod Top-level yang (sub)module @@ -2083,7 +1911,7 @@ yang_parse_filename(const char *filename, int fd = -1; struct stat st; - // clicon_debug(1, "%s %s", __FUNCTION__, filename); + clicon_debug(1, "%s %s", __FUNCTION__, filename); if (stat(filename, &st) < 0){ clicon_err(OE_YANG, errno, "%s not found", filename); goto done; @@ -2258,7 +2086,7 @@ yang_features(clicon_handle h, while (iys_len){ /* Note, children may be removed */ ys = yt->ys_stmt[i]; if (ys->ys_keyword == Y_IF_FEATURE){ - if (yang_nodeid_split(ys->ys_argument, &prefix, &feature) < 0) + if (nodeid_split(ys->ys_argument, &prefix, &feature) < 0) goto done; /* Specifically need to handle? strcmp(prefix, myprefix)) */ if (prefix == NULL) @@ -2680,7 +2508,6 @@ yang_abs_schema_nodeid(yang_spec *yspec, char *id; char *prefix = NULL; yang_stmt *yprefix; - yang_stmt *ys; /* check absolute schema_nodeid */ if (schema_nodeid[0] != '/'){ @@ -2719,21 +2546,6 @@ yang_abs_schema_nodeid(yang_spec *yspec, } } } - if (ymod == NULL){ /* Try find id from topnode without prefix XXX remove?*/ - if ((ys = yang_find_topnode(yspec, id, YC_SCHEMANODE)) == NULL){ - clicon_err(OE_YANG, 0, "Module with id:\"%s:%s\" not found", prefix,id); - goto done; - } - if ((ymod = ys_module(ys)) == NULL){ - clicon_err(OE_YANG, 0, "Module with id:%s:%s not found2", prefix,id); - goto done; - } - if ((yprefix = yang_find((yang_node*)ymod, Y_PREFIX, NULL)) != NULL && - strcmp(yprefix->ys_argument, prefix) != 0){ - clicon_err(OE_YANG, 0, "Module with id:\"%s:%s\" not found", prefix,id); - goto done; - } - } if (schema_nodeid_vec((yang_node*)ymod, vec+1, nvec-1, keyword, yres) < 0) goto done; ok: /* yres may not be set */ diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c index 6e08c96e..c0b1e408 100644 --- a/lib/src/clixon_yang_module.c +++ b/lib/src/clixon_yang_module.c @@ -81,7 +81,7 @@ int yang_modules_init(clicon_handle h) { - int retval = -1; + int retval = -1; yang_spec *yspec; yspec = clicon_dbspec_yang(h); @@ -203,7 +203,7 @@ yang_modules_state_get(clicon_handle h, cprintf(cb,"%s", ys->ys_argument); else cprintf(cb,""); - cprintf(cb, "implement"); + /* This follows order in rfc 7895: feature, conformance-type, submodules */ yc = NULL; while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) { switch(yc->ys_keyword){ @@ -211,6 +211,14 @@ yang_modules_state_get(clicon_handle h, if (yc->ys_cv && cv_bool_get(yc->ys_cv)) cprintf(cb,"%s", yc->ys_argument); break; + default: + break; + } + } + cprintf(cb, "implement"); + yc = NULL; + while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) { + switch(yc->ys_keyword){ case Y_SUBMODULE: cprintf(cb,""); cprintf(cb,"%s", yc->ys_argument); diff --git a/test/all.sh b/test/all.sh index 79a0309f..1e036ab1 100755 --- a/test/all.sh +++ b/test/all.sh @@ -38,6 +38,7 @@ if [ $err -eq 0 ]; then else echo -e "\e[31mError" echo -ne "\e[0m" + exit -1 fi diff --git a/test/test_cli.sh b/test/test_cli.sh index aa1379d9..b4005433 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -59,7 +59,7 @@ expectfn "$clixon_cli -1 -f $cfg delete interfaces" 0 "^$" new "cli show configuration delete top" expectfn "$clixon_cli -1 -f $cfg show conf cli" 0 "^$" -new "cli configure" +new "cli configure set interfaces" expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0" 0 "^$" new "cli show configuration" diff --git a/test/test_compat.sh b/test/test_compat.sh new file mode 100755 index 00000000..77875410 --- /dev/null +++ b/test/test_compat.sh @@ -0,0 +1,116 @@ +#!/bin/bash +# Test of backward compatibility +# 1) Load <3.9 startup/running/extra files without namespaces - ensure it returns namespaces +# + +APPNAME=example +# include err() and new() functions and creates $dir +. ./lib.sh +cfg=$dir/conf_startup.xml + +cat < $cfg + + $cfg + /usr/local/share/$APPNAME/yang + /usr/local/share/clixon + example + $APPNAME + /usr/local/lib/$APPNAME/backend + /usr/local/lib/$APPNAME/netconf + /usr/local/lib/$APPNAME/restconf + /usr/local/lib/$APPNAME/cli + /usr/local/lib/$APPNAME/clispec + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + 1 + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + 0 + init + true + + +EOF + + +run(){ + mode=$1 + expect=$2 + + dbdir=$dir/db + cat < $dbdir + + + + run + ex:eth + + + +EOF + sudo mv $dbdir /usr/local/var/$APPNAME/running_db + + cat < $dbdir + + + + startup + ex:eth + + + +EOF + sudo mv $dbdir /usr/local/var/$APPNAME/startup_db + + cat < $dir/config + + + + extra + ex:eth + + + +EOF + + new "test params: -f $cfg -s $mode -c $dir/config" + + if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + + new "start backend -f $cfg -s $mode -c $dir/config" + sudo $clixon_backend -f $cfg -s $mode -c $dir/config + if [ $? -ne 0 ]; then + err + fi + fi + + new "Check $mode" + expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^$expect]]>]]>$" + + if [ $BE -eq 0 ]; then + exit # BE + fi + + 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 + sudo clixon_backend -z -f $cfg + if [ $? -ne 0 ]; then + err "kill backend" + fi + +} # run + +run running 'extraex:ethtrueloex:loopbacktruerunex:ethtrue' +run startup 'extraex:ethtrueloex:loopbacktruestartupex:ethtrue' + +rm -rf $dir diff --git a/test/test_datastore.sh b/test/test_datastore.sh index bcc7b27b..9744413d 100755 --- a/test/test_datastore.sh +++ b/test/test_datastore.sh @@ -50,9 +50,6 @@ EOF xml='12first-entry13second-entry23third-entryabcastring' -# Without xmlns -xmlxxx='12first-entry13second-entry23third-entryabcastring' - run(){ name=$1 mydir=$dir/$name @@ -72,7 +69,7 @@ run(){ expectmatch "$ret" $? "0" "" new "datastore $name get" - expectfn "$datastore $conf get /" 0 "^$xmlxxx$" + expectfn "$datastore $conf get /" 0 "^$xml$" new "datastore $name put all remove" expectfn "$datastore $conf put remove " 0 "" @@ -87,7 +84,7 @@ run(){ # expectfn "$datastore $conf put merge $xml" 0 "" new "datastore $name get" - expectfn "$datastore $conf get /" 0 "^$xmlxxx$" + expectfn "$datastore $conf get /" 0 "^$xml$" new "datastore $name put all delete" expectfn "$datastore $conf put remove " 0 "" @@ -100,7 +97,7 @@ run(){ expectmatch "$ret" $? "0" "" new "datastore $name get" - expectfn "$datastore $conf get /" 0 "^$xmlxxx$" + expectfn "$datastore $conf get /" 0 "^$xml$" new "datastore $name put top create" expectfn "$datastore $conf put create " 0 "" # error diff --git a/test/test_feature.sh b/test/test_feature.sh index dfb99172..20adfa3f 100755 --- a/test/test_feature.sh +++ b/test/test_feature.sh @@ -90,13 +90,13 @@ new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf enabled feature" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'foo]]>]]>' "^]]>]]>$" new "netconf validate enabled feature" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf disabled feature" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" '^applicationoperation-failederrorValidation failed]]>]]>$' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'foo]]>]]>' '^applicationunknown-elementyerrorUnassigned yang spec]]>]]>$' # This test has been broken up into all different modules instead of one large # reply since the modules change so often @@ -105,6 +105,13 @@ ret=$($clixon_netconf -qf $cfg -y $fyang<]]>]]> EOF ) +new "netconf modules-state header" +expect='^' +match=`echo "$ret" | grep -GZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + new "netconf module A" expect="exampleurn:example:clixonAimplement" match=`echo "$ret" | grep -GZo "$expect"` @@ -135,8 +142,9 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi +# Note order of features in ietf-netconf yang is alphabetically: candidate, startup, validate, xpath new "netconf module ietf-netconf" -expect="module>ietf-netconf2011-06-01urn:ietf:params:xml:ns:netconf:base:1.0candidatestartupvalidatexpathimplement" +expect="ietf-netconf2011-06-01urn:ietf:params:xml:ns:netconf:base:1.0candidatestartupvalidatexpathimplement" match=`echo "$ret" | grep -GZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" diff --git a/test/test_identity.sh b/test/test_identity.sh index 14d6b996..eeaa6198 100755 --- a/test/test_identity.sh +++ b/test/test_identity.sh @@ -122,44 +122,44 @@ if [ $BE -ne 0 ]; then fi new "Set crypto to aes" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'aes]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'aes]]>]]>' '^]]>]]>$' new "netconf validate " expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "Set crypto to mc:aes" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "mc:aes]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'mc:aes]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "Set crypto to des:des3" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "des:des3]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'des:des3]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "Set crypto to mc:foo" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "mc:foo]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'mc:foo]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "Set crypto to des:des3 using xmlns" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "des:des3]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'des:des3]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" # XXX this is not supported #new "Set crypto to x:des3 using xmlns" -#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "x:des3]]>]]>" "^]]>]]>$" +#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'x:des3]]>]]>' "^]]>]]>$" -#new "netconf validate" -#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +new "netconf validate" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "Set crypto to foo:bar" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo:bar]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'foo:bar]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorIdentityref validation failed, foo:bar not derived from crypto-alg]]>]]>$" diff --git a/test/test_leafref.sh b/test/test_leafref.sh index 6e66c7f3..63c6a270 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -88,22 +88,19 @@ if [ $BE -ne 0 ]; then fi new "leafref base config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " -eth0ex:eth
192.0.2.1
192.0.2.2
-loex:lo
127.0.0.1
-
]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth0ex:eth
192.0.2.1
192.0.2.2
loex:lo
127.0.0.1
]]>]]>' '^]]>]]>$' new "leafref get config" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^eth0' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^eth0' new "leafref base commit" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "leafref get config" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^eth0' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^eth0' new "leafref add wrong ref" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth3
10.0.4.6
]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth3
10.0.4.6
]]>]]>' '^]]>]]>$' new "leafref validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationbad-elementeth3errorLeafref validation failed: No such leaf]]>]]>$' @@ -112,10 +109,10 @@ new "leafref discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "leafref add correct absref" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth0]]>]]>' '^]]>]]>$' new "leafref add correct relref" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth0]]>]]>' '^]]>]]>$' # XXX add address @@ -123,7 +120,7 @@ new "leafref validate (ok)" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^" new "leafref delete leaf" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth0]]>]]>" "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth0]]>]]>' '^' new "leafref validate (should fail)" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationbad-elementeth0errorLeafref validation failed: No such leaf]]>]]>$' diff --git a/test/test_list.sh b/test/test_list.sh index 37fb8c6c..1b9bdc2e 100755 --- a/test/test_list.sh +++ b/test/test_list.sh @@ -81,19 +81,19 @@ if [ $BE -ne 0 ]; then fi new "minmax: minimal" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace00]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace00]]>]]>' "^]]>]]>$" new "minmax: minimal validate ok" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "minmax: maximal" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace01unbounded0101unbounded01]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace01unbounded0101unbounded01]]>]]>' '^]]>]]>$' new "minmax: validate ok" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "minmax: empty" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace]]>]]>' '^]]>]]>$' # NYI if false; then @@ -101,25 +101,25 @@ new "minmax: validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "minmax: no list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace0]]>]]>' '^]]>]]>$' new "minmax: validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "minmax: no leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace0]]>]]>' '^]]>]]>$' new "minmax: validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "minmax: Too large list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace0120]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace0120]]>]]>' '^]]>]]>$' new "minmax: validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "minmax: Too large leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace0012]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace0012]]>]]>' '^]]>]]>$' new "minmax: validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" diff --git a/test/test_nacm.sh b/test/test_nacm.sh index 389ae010..aac4be87 100755 --- a/test/test_nacm.sh +++ b/test/test_nacm.sh @@ -49,7 +49,7 @@ EOF # The groups are slightly modified from RFC8341 A.1 # The rule-list is from A.2 RULES=$(cat < + false deny deny @@ -99,7 +99,7 @@ RULES=$(cat < - 0 + 0 EOF ) @@ -131,7 +131,7 @@ new "restconf DELETE whole datastore" expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" "" new2 "auth get" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" 'null +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" 'null ' new "auth set authentication config" @@ -147,33 +147,33 @@ new2 "auth get (wrong passwd: access denied)" expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (access)" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' #----------------Enable NACM new "enable nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" "" new2 "admin get nacm" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' new2 "limited get nacm" -expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' new2 "guest get nacm" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "admin edit nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"example:x": 1}' http://localhost/restconf/data/example:x)" "" new2 "limited edit nacm" -expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' +expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"example:x": 2}' http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' new2 "guest edit nacm" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"example:x": 3}' http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index 9bdee51d..b706b96f 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -163,11 +163,11 @@ new "restconf DELETE whole datastore" expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" "" new2 "auth get" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/state)" '{"state": {"op": "42"}} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:state)" '{"example:state": {"op": "42"}} ' new "Set x to 0" -expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 0}' http://localhost/restconf/data/x)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"example:x": 0}' http://localhost/restconf/data/example:x)" "" new2 "auth get (no user: access denied)" expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' @@ -176,28 +176,28 @@ new2 "auth get (wrong passwd: access denied)" expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (access)" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' new2 "admin get nacm" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' new2 "limited get nacm" -expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' new2 "guest get nacm" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "admin edit nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"example:x": 1}' http://localhost/restconf/data/example:x)" "" new2 "limited edit nacm" -expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' +expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' new2 "guest edit nacm" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "cli show conf as admin" expectfn "$clixon_cli -1 -U andy -l o -f $cfg -y $fyang show conf" 0 "^x 1;$" diff --git a/test/test_nacm_protocol.sh b/test/test_nacm_protocol.sh index 963218e5..b4e5bc2f 100755 --- a/test/test_nacm_protocol.sh +++ b/test/test_nacm_protocol.sh @@ -122,7 +122,7 @@ RULES=$(cat < - 0 + 0 EOF ) @@ -158,10 +158,10 @@ new "commit it" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "enable nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" "" new2 "admin get nacm" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' # Rule 1: deny-kill-session @@ -186,7 +186,7 @@ new "deny-delete-config: limited fail (restconf) ok" expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data)" '' new2 "admin get nacm (should be null)" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" 'null +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" 'null ' new "deny-delete-config: admin ok (restconf)" @@ -200,14 +200,14 @@ new "commit it" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "enable nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" "" # Rule 3: permit-edit-config new "permit-edit-config: limited ok restconf" -expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '' +expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"example:x": 2}' http://localhost/restconf/data/example:x)" '' new2 "permit-edit-config: guest fail restconf" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"example:x": 2}' http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" diff --git a/test/test_netconf.sh b/test/test_netconf.sh index 7b060875..ced3047d 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -114,14 +114,13 @@ new "netconf get-config single quotes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' new "Add subtree eth/0/0 using none which should not change anything" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "noneeth/0/0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'noneeth/0/0]]>]]>' "^]]>]]>$" new "Check nothing added" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^]]>]]>$' new "Add subtree eth/0/0 using none and create which should add eth/0/0" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' "^]]>]]>$" - +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' "^]]>]]>$" # Too many quotes, (single inside double inside single) need to fool bash cat < $tmp # new @@ -129,22 +128,22 @@ cat < $tmp # new EOF new "Check eth/0/0 added using xpath" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" "^eth/0/0ex:ethtrue]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" '^eth/0/0ex:ethtrue]]>]]>$' new "Re-create same eth/0/0 which should generate error" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' '^' new "Delete eth/0/0 using none config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' '^]]>]]>$' new "Check deleted eth/0/0 (non-presence container)" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^]]>]]>$' new "Re-Delete eth/0/0 using none should generate error" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' '^' new "netconf edit config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth/0/0eth1true
9.2.3.424
]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0eth1true
9.2.3.424
]]>]]>' "^]]>]]>$" # Too many quotes cat < $tmp # new @@ -152,7 +151,7 @@ cat < $tmp # new EOF new "netconf get config xpath" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" "^eth1true]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" '^eth1true]]>]]>$' # Too many quotes cat < $tmp # new @@ -160,7 +159,7 @@ cat < $tmp # new EOF new "netconf get config xpath parent" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" "^eth/0/0trueeth1truetruefalse
9.2.3.424
]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" '^eth/0/0trueeth1truetruefalse
9.2.3.424
]]>]]>$' new "netconf validate missing type" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^" @@ -172,13 +171,13 @@ new "netconf get empty config2" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf edit extra xml" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf edit config eth1" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth1ex:eth]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth1ex:eth]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -187,32 +186,32 @@ new "netconf commit" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf edit config merge" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth2ex:ethmerge]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth2ex:ethmerge]]>]]>' "^]]>]]>$" new "netconf edit ampersand encoding(<&): name:'eth&' type:'t<>'" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth&t<>]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth&t<>]]>]]>' '^]]>]]>$' new "netconf get replaced config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^eth&t<>trueeth1ex:ethtrueeth2ex:ethtrue]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^eth&t<>trueeth1ex:ethtrueeth2ex:ethtrue]]>]]>$' new "cli show configuration eth& - encoding tests" expectfn "$clixon_cli -1 -f $cfg -y $fyang show conf cli" 0 "interfaces interface eth& type t<> interfaces interface eth& enabled true" new "netconf edit CDATA" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth/0/0ex:eth]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:eth]]>]]>' "^]]>]]>$" #new "netconf get CDATA" -#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "eth/0/0true]]>]]>" +#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'eth/0/0true]]>]]>' new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf edit state operation should fail" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "42]]>]]>" "^protocolinvalid-value" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '42]]>]]>' "^protocolinvalid-value" new "netconf get state operation" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^42]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^42]]>]]>$' new "netconf lock/unlock" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>]]>]]>" "^]]>]]>]]>]]>$" @@ -234,7 +233,7 @@ new "copy startup" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf get startup" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^eth1ex:ethtrue]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^eth1ex:ethtrue]]>]]>$' new "netconf delete startup" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -243,16 +242,16 @@ new "netconf check empty startup" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf rpc" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'ipv4ipv4]]>]]>' "^ipv4" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'ipv4ipv4]]>]]>' '^ipv4' -new "netconf rpc without namespace (iterate kludge should work)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ipv4ipv4]]>]]>" "^ipv4" +#new "netconf rpc without namespace (iterate kludge should work)" +#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ipv4ipv4]]>]]>" "^ipv4" new "netconf empty rpc" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" new "netconf client-side rpc" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "example]]>]]>" "^ok]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'example]]>]]>' '^ok]]>]]>$' if [ $BE -eq 0 ]; then exit # BE diff --git a/test/test_order.sh b/test/test_order.sh index cb9eb780..4db73e9c 100755 --- a/test/test_order.sh +++ b/test/test_order.sh @@ -16,7 +16,6 @@ fyang=$dir/order.yang dbdir=$dir/order -new "Set up $dbdir" rm -rf $dbdir if [ ! -d $dbdir ]; then mkdir $dbdir @@ -84,30 +83,32 @@ module example{ } EOF +rm -f $dbdir/candidate_db +# alt cat < $dbdir/running_db - d - d - dbar - dbar - b - b - hej - c - c - abar - abar - hopp - a - a - cbar - cbar - bbar - bbar + d + d + dbar + dbar + b + b + hej + c + c + abar + abar + hopp + a + a + cbar + cbar + bbar + bbar EOF -new "test params: -f $cfg -y $fyang" +new "test params: -s running -f $cfg -y $fyang" if [ $BE -ne 0 ]; then new "kill old backend" @@ -123,20 +124,20 @@ if [ $BE -ne 0 ]; then fi # Check as file -new "verify running from start, should be: l,c,y0,y1,y2,y3; y1 and y3 sorted. Note this fails if XML_SORT set to false" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhoppdbcaabcddbarabarcbarbbarabarbbarcbardbar]]>]]>$" +new "verify running from start, should be: c,l,y0,y1,y2,y3; y1 and y3 sorted. Note this fails if CLICON_XML_SORT set to false" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^hejhoppdbcaabcddbarabarcbarbbarabarbbarcbardbar]]>]]>$' new "get each ordered-by user leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^abar]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^abar]]>]]>$' new "get each ordered-by user leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^abar]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^abar]]>]]>$' new "get each ordered-by user leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^bbar]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^bbar]]>]]>$' new "get each ordered-by user leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^bbar]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^bbar]]>]]>$' new "delete candidate" expecteof "$clixon_netconf -qf $cfg" 0 'none]]>]]>' "^]]>]]>$" @@ -144,33 +145,33 @@ expecteof "$clixon_netconf -qf $cfg" 0 'cb]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'cb]]>]]>' "^]]>]]>$" new "add one entry to leaf-list user order" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "a]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'a]]>]]>' "^]]>]]>$" new "netconf commit" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "add one entry to leaf-list user order after commit" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '0]]>]]>' "^]]>]]>$" new "netconf commit" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "verify leaf-list user order in running (as entered)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^cba0]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^cba0]]>]]>$' # LISTS new "add two entries to list user order" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "cbarbfoo]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'cbarbfoo]]>]]>' "^]]>]]>$" new "add one entry to list user order" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "afie]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'afie]]>]]>' "^]]>]]>$" new "verify list user order (as entered)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^cbarbfooafie]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^cbarbfooafie]]>]]>$' if [ $BE -eq 0 ]; then exit # BE diff --git a/test/test_perf.sh b/test/test_perf.sh index 4d9d6dfc..eb1fe687 100755 --- a/test/test_perf.sh +++ b/test/test_perf.sh @@ -83,7 +83,7 @@ sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG" -s /bin/sh www-data & sleep $RCWAIT new "generate 'large' config with $number list entries" -echo -n "" > $fconfig +echo -n "" > $fconfig for (( i=0; i<$number; i++ )); do echo -n "$i$i" >> $fconfig done @@ -111,7 +111,7 @@ new "netconf commit large config again" expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf add small (1 entry) config" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "xy]]>]]>" "^]]>]]>$" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 'xy]]>]]>' "^]]>]]>$" new "netconf get $req small config" time -p for (( i=0; i<$req; i++ )); do @@ -128,7 +128,7 @@ done new "netconf add $req small config" time -p for (( i=0; i<$req; i++ )); do rnd=$(( ( RANDOM % $number ) )) - echo "$rnd$rnd]]>]]>" + echo "$rnd$rnd]]>]]>" done | $clixon_netconf -qf $cfg -y $fyang > /dev/null new "netconf add $req restconf small config" @@ -138,10 +138,10 @@ time -p for (( i=0; i<$req; i++ )); do done new "netconf get large config" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^0011" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^0011' new "generate large leaf-list config" -echo -n "replace" > $fconfig +echo -n "replace" > $fconfig for (( i=0; i<$number; i++ )); do echo -n "$i" >> $fconfig done @@ -158,17 +158,17 @@ expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "$rnd]]>]]>" + echo "$rnd]]>]]>" done | $clixon_netconf -qf $cfg -y $fyang > /dev/null new "netconf add small leaf-list config" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "x]]>]]>" "^]]>]]>$" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 'x]]>]]>' "^]]>]]>$" new "netconf commit small leaf-list config" expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf get large leaf-list config" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^01" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^01' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 1874a0b3..e7451d0a 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -76,7 +76,7 @@ module example{ EOF # This is a fixed 'state' implemented in routing_backend. It is assumed to be always there -state='{"state": {"op": "42"}}' +state='{"example:state": {"op": "42"}}' new "test params: -f $cfg -y $fyang" if [ $BE -ne 0 ]; then @@ -102,9 +102,6 @@ sleep $RCWAIT new "restconf tests" -new2 "restconf rpc using POST json without mandatory element" -expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "wrongelement"},"error-severity": "error"}}} ' - new2 "restconf root discovery. RFC 8040 3.1 (xml+xrd)" expecteq "$(curl -s -X GET http://localhost/.well-known/host-meta)" " @@ -119,13 +116,14 @@ new2 "restconf get restconf resource. RFC 8040 3.3 (xml)" expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/restconf)" '2016-06-21 ' +# Should be alphabetically ordered new2 "restconf get restconf/operations. RFC8040 3.3.2 (json)" -expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"example:client-rpc": null,"ietf-routing:fib-route": null,"ietf-routing:route-count": null,"example:empty": null}} +expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"example:empty": null,"example:client-rpc": null}{"ietf-routing:fib-route": null,"ietf-routing:route-count": null}} ' new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) -expect='' +expect='' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" @@ -142,8 +140,8 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi -new2 "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895" -expecteq "$(curl -s -H 'Accept: application/yang-data+json' -G http://localhost/restconf/data/ietf-yang-library:modules-state/module=ietf-routing,2014-10-26/)" '{"module": [{"name": "ietf-routing","revision": "2014-10-26","namespace": "urn:ietf:params:xml:ns:yang:ietf-routing","conformance-type": "implement"}]} +new2 "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895 (explicit resource)" +expecteq "$(curl -s -H 'Accept: application/yang-data+json' -G http://localhost/restconf/data/ietf-yang-library:modules-state/module=ietf-routing,2014-10-26/)" '{"ietf-yang-library:module": [{"name": "ietf-routing","revision": "2014-10-26","namespace": "urn:ietf:params:xml:ns:yang:ietf-routing","conformance-type": "implement"}]} ' new "restconf options. RFC 8040 4.1" @@ -154,132 +152,133 @@ expectfn "curl -s -I http://localhost/restconf/data" 0 "HTTP/1.1 200 OK" #Content-Type: application/yang-data+json" new "restconf empty rpc" -expecteq "$(curl -s -X POST -d {\"input\":null} http://localhost/restconf/operations/example:empty)" "" +expecteq "$(curl -s -X POST -d {\"example:input\":null} http://localhost/restconf/operations/example:empty)" "" new2 "restconf empty rpc with extra args (should fail)" -expecteq "$(curl -s -X POST -d {\"input\":{\"extra\":null}} http://localhost/restconf/operations/example:empty)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}} ' +expecteq "$(curl -s -X POST -d {\"example:input\":{\"extra\":null}} http://localhost/restconf/operations/example:empty)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}} ' new2 "restconf get empty config + state json" -expecteq "$(curl -sSG http://localhost/restconf/data/state)" '{"state": {"op": "42"}} +expecteq "$(curl -sSG http://localhost/restconf/data/example:state)" '{"example:state": {"op": "42"}} ' new2 "restconf get empty config + state json + module" -expecteq "$(curl -sSG http://localhost/restconf/data/example:state)" '{"state": {"op": "42"}} +expecteq "$(curl -sSG http://localhost/restconf/data/example:state)" '{"example:state": {"op": "42"}} ' new2 "restconf get empty config + state json with wrong module name" -expecteq "$(curl -sSG http://localhost/restconf/data/badmodule:state)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "No yang node found: badmodule:state"}}} ' +expecteq "$(curl -sSG http://localhost/restconf/data/badmodule:state)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "No such yang module: badmodule"}}} ' new "restconf get empty config + state xml" -ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/state) -expect="42" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/example:state) +expect='42' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new2 "restconf get data/ json" -expecteq "$(curl -s -G http://localhost/restconf/data/state/op=42)" '{"op": "42"} +expecteq "$(curl -s -G http://localhost/restconf/data/example:state/op=42)" '{"example:op": "42"} ' new "restconf get state operation eth0 xml" # Cant get shell macros to work, inline matching from lib.sh -ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/state/op=42) -expect="42" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/example:state/op=42) +expect='42' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new2 "restconf get state operation eth0 type json" -expecteq "$(curl -s -G http://localhost/restconf/data/state/op=42)" '{"op": "42"} +expecteq "$(curl -s -G http://localhost/restconf/data/example:state/op=42)" '{"example:op": "42"} ' new "restconf get state operation eth0 type xml" # Cant get shell macros to work, inline matching from lib.sh -ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/state/op=42) -expect="42" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/example:state/op=42) +expect='42' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new2 "restconf GET datastore" -expecteq "$(curl -s -X GET http://localhost/restconf/data/state)" '{"state": {"op": "42"}} +expecteq "$(curl -s -X GET http://localhost/restconf/data/example:state)" '{"example:state": {"op": "42"}} ' # Exact match new "restconf Add subtree to datastore using POST" -expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 'HTTP/1.1 200 OK' +expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 'HTTP/1.1 200 OK' new "restconf Re-add subtree which should give error" -expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' +expectfn 'curl -s -X POST -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' # XXX Cant get this to work #expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"ex:eth\",\"enabled\":true}}} http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' new "restconf Check interfaces eth/0/0 added" -expectfn "curl -s -G http://localhost/restconf/data" 0 '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "ex:eth","enabled": true}\]},"state": {"op": "42"}} - ' +expectfn "curl -s -G http://localhost/restconf/data" 0 '"ietf-interfaces:interfaces": {"interface": \[{"name": "eth/0/0","type": "ex:eth","enabled": true}\]}' new "restconf delete interfaces" -expecteq $(curl -s -X DELETE http://localhost/restconf/data/interfaces) "" +expecteq $(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces) "" new "restconf Check empty config" -expectfn "curl -sG http://localhost/restconf/data/state" 0 "$state" +expectfn "curl -sG http://localhost/restconf/data/example:state" 0 "$state" +# XXX: gives +# new "restconf Add interfaces subtree eth/0/0 using POST" -expectfn 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}} http://localhost/restconf/data/interfaces' 0 "" +expectfn 'curl -s -X POST -d {"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}} http://localhost/restconf/data/ietf-interfaces:interfaces' 0 "" # XXX cant get this to work #expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type\":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces)" "" new2 "restconf Check eth/0/0 added config" -expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]}} +expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]}} ' new2 "restconf Check eth/0/0 added state" -expecteq "$(curl -s -G http://localhost/restconf/data/state)" '{"state": {"op": "42"}} +expecteq "$(curl -s -G http://localhost/restconf/data/example:state)" '{"example:state": {"op": "42"}} ' new2 "restconf Re-post eth/0/0 which should generate error" -expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' +expecteq "$(curl -s -X POST -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new "Add leaf description using POST" -expecteq "$(curl -s -X POST -d '{"description":"The-first-interface"}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" +expecteq "$(curl -s -X POST -d '{"ietf-interfaces:description":"The-first-interface"}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" "" new "Add nothing using POST" -expectfn 'curl -s -X POST http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": " on line 1: syntax error at or before:' +expectfn 'curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": " on line 1: syntax error at or before:' new2 "restconf Check description added" -expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "ex:eth","enabled": true}]}} +expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "ex:eth","enabled": true}]}} ' new "restconf delete eth/0/0" -expecteq "$(curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" +expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" "" new "Check deleted eth/0/0" expectfn 'curl -s -G http://localhost/restconf/data' 0 $state new2 "restconf Re-Delete eth/0/0 using none should generate error" -expecteq "$(curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-missing","error-severity": "error","error-message": "Data does not exist; cannot delete resource"}}} ' +expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-missing","error-severity": "error","error-message": "Data does not exist; cannot delete resource"}}} ' new "restconf Add subtree eth/0/0 using PUT" -expecteq "$(curl -s -X PUT -d '{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" +expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" "" new2 "restconf get subtree" -expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]}} +expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]}} ' new2 "restconf rpc using POST json" -expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4","destination-address":{"address-family":"ipv6"}}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"},"source-protocol": "static"}}} +expecteq "$(curl -s -X POST -d '{"ietf-routing:input":{"routing-instance-name":"ipv4","destination-address":{"address-family":"ipv6"}}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-routing:output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"},"source-protocol": "static"}}} ' # Cant get this to work due to quoting #new2 "restconf rpc using POST wrong JSON" -#expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":ipv4}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": " on line 1: syntax error at or before: i"}}} ' +#expecteq "$(curl -s -X POST -d '{"ietf-routing:input":{"routing-instance-name":ipv4}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": " on line 1: syntax error at or before: i"}}} ' new2 "restconf rpc using POST json without mandatory element" -expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "wrongelement"},"error-severity": "error"}}} ' +expecteq "$(curl -s -X POST -d '{"ietf-routing:input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "wrongelement"},"error-severity": "error"}}} ' new2 "restconf rpc non-existing rpc without namespace" expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "kalle"},"error-severity": "error","error-message": "RPC not defined"}}} ' @@ -291,29 +290,29 @@ new2 "restconf rpc missing name" expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "Operation name expected"}}} ' new2 "restconf rpc missing input" -expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "routing-instance-name"},"error-severity": "error","error-message": "Mandatory variable"}}} ' +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": "restconf RPC does not have input statement"}}} ' new "restconf rpc using POST xml" -ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route) -expect="ipv42.3.4.5static" +ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"ietf-routing:input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route) +expect='ipv42.3.4.5static' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new2 "restconf rpc using wrong prefix" -expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' +expecteq "$(curl -s -X POST -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' new "restconf local client rpc using POST xml" -ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"request":"example"}}' http://localhost/restconf/operations/example:client-rpc) -expect="ok" +ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"example:input":{"request":"example"}}' http://localhost/restconf/operations/example:client-rpc) +expect='ok' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi # XXX cant get -H to work -#expecteq 'curl -s -X POST -H "Accept: application/yang-data+xml" -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/ietf-routing:fib-route' 'ipv42.3.4.5' +#expecteq 'curl -s -X POST -H "Accept: application/yang-data+xml" -d {"ietf-routing:input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/ietf-routing:fib-route' 'ipv42.3.4.5' # Cant get shell macros to work, inline matching from lib.sh diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index 1cf7188f..68677250 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -72,73 +72,82 @@ sleep $RCWAIT new "restconf tests" new "restconf POST initial tree" -expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 "" +expectfn 'curl -s -X POST -d {"example:cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 "" new "restconf GET datastore intial" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 '{"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' -new "restconf GET interface" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0" 0 '{"interface": \[{"name": "local0","type": "regular"}\]}' +new "restconf GET interface subtree" +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1/interface=local0" 0 '{"example:interface": \[{"name": "local0","type": "regular"}\]}' + +new "restconf GET interface subtree xml" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/example:cont1/interface=local0) +expect='local0regular' +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi new "restconf GET if-type" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0/type" 0 '{"type": "regular"}' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1/interface=local0/type" 0 '{"example:type": "regular"}' new "restconf POST interface without mandatory type" -expectfn 'curl -s -X POST -d {"interface":{"name":"TEST"}} http://localhost/restconf/data/cont1' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "type"},"error-severity": "error","error-message": "Mandatory variable"}}} ' +expectfn 'curl -s -X POST -d {"interface":{"name":"TEST"}} http://localhost/restconf/data/example:cont1' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "type"},"error-severity": "error","error-message": "Mandatory variable"}}} ' new "restconf POST interface" -expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' 0 "" +expectfn 'curl -s -X POST -d {"example:interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/example:cont1' 0 "" +# XXX should it be example:interface? new2 "restconf POST again" -expecteq "$(curl -s -X POST -d '{"interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/cont1)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' +expecteq "$(curl -s -X POST -d '{"interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/example:cont1)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new2 "restconf POST from top" -expecteq "$(curl -s -X POST -d '{"cont1":{"interface":{"name":"TEST","type":"eth0"}}}' http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' +expecteq "$(curl -s -X POST -d '{"example:cont1":{"interface":{"name":"TEST","type":"eth0"}}}' http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new "restconf DELETE" -expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' 0 "" +expectfn 'curl -s -X DELETE http://localhost/restconf/data/example:cont1' 0 "" new "restconf GET null datastore" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 'null' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 'null' new "restconf POST initial tree" -expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 "" +expectfn 'curl -s -X POST -d {"example:cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 "" new "restconf GET initial tree" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 '{"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' new "restconf DELETE whole datastore" expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 "" new "restconf GET null datastore" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 'null' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 'null' new "restconf PUT initial datastore" -expectfn 'curl -s -X PUT -d {"data":{"cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 "" +expectfn 'curl -s -X PUT -d {"data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 "" new "restconf GET datastore" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 '{"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' new "restconf PUT replace datastore" -expectfn 'curl -s -X PUT -d {"data":{"cont2":{"name":"foo"}}} http://localhost/restconf/data' 0 "" +expectfn 'curl -s -X PUT -d {"data":{"example:cont2":{"name":"foo"}}} http://localhost/restconf/data' 0 "" new "restconf GET replaced datastore" -expectfn "curl -s -X GET http://localhost/restconf/data/cont2" 0 '{"cont2": {"name": "foo"}}' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont2" 0 '{"example:cont2": {"name": "foo"}}' new "restconf PUT initial datastore again" -expectfn 'curl -s -X PUT -d {"data":{"cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 "" +expectfn 'curl -s -X PUT -d {"data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 "" new "restconf PUT change interface" -expectfn 'curl -s -X PUT -d {"interface":{"name":"local0","type":"atm0"}} http://localhost/restconf/data/cont1/interface=local0' 0 "" +expectfn 'curl -s -X PUT -d {"interface":{"name":"local0","type":"atm0"}} http://localhost/restconf/data/example:cont1/interface=local0' 0 "" new "restconf GET datastore atm" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 '{"cont1": {"interface": \[{"name": "local0","type": "atm0"}\]}}' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1": {"interface": \[{"name": "local0","type": "atm0"}\]}}' new "restconf PUT add interface" -expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' 0 "" +expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/example:cont1/interface=TEST' 0 "" new "restconf PUT change key error" -expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "api-path keys do not match data keys"}}}' +expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/example:cont1/interface=TEST' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "api-path keys do not match data keys"}}}' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" diff --git a/test/test_rpc.sh b/test/test_rpc.sh index f28a4df9..f033dfb0 100755 --- a/test/test_rpc.sh +++ b/test/test_rpc.sh @@ -53,50 +53,70 @@ sleep $RCWAIT new "rpc tests" # 1.First some positive tests vary media types -# +# extra complex because pattern matching on return haders +new "restconf empty rpc" +ret=$(curl -is -X POST http://localhost/restconf/operations/example:empty) +expect="204 No Content" +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + +new "netconf empty rpc" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^]]>]]>$' + new2 "restconf example rpc json/json default - no http media headers" -expecteq "$(curl -s -X POST -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "42"}} +expecteq "$(curl -s -X POST -d '{"example:input":{"x":0}}' http://localhost/restconf/operations/example:example)" '{"example:output": {"x": "0","y": "42"}} ' new2 "restconf example rpc json/json change y default" -expecteq "$(curl -s -X POST -d '{"input":{"x":"0","y":"99"}}' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "99"}} +expecteq "$(curl -s -X POST -d '{"example:input":{"x":"0","y":"99"}}' http://localhost/restconf/operations/example:example)" '{"example:output": {"x": "0","y": "99"}} ' new2 "restconf example rpc json/json" # XXX example:input example:output -expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+json' -H 'Content-Type: application/yang-data+json' -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "42"}} +expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+json' -H 'Content-Type: application/yang-data+json' -d '{"example:input":{"x":"0"}}' http://localhost/restconf/operations/example:example)" '{"example:output": {"x": "0","y": "42"}} ' new2 "restconf example rpc xml/json" -expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' -H 'Content-Type: application/yang-data+json' -d '0' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "42"}} +expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' -H 'Content-Type: application/yang-data+json' -d '0' http://localhost/restconf/operations/example:example)" '{"example:output": {"x": "0","y": "42"}} ' new2 "restconf example rpc json/xml" -# 042 +expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+json' -H 'Accept: application/yang-data+xml' -d '{"example:input":{"x":"0"}}' http://localhost/restconf/operations/example:example)" '042 ' new2 "restconf example rpc xml/xml" -# 0' http://localhost/restconf/operations/example:example)" '042 +expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '0' http://localhost/restconf/operations/example:example)" '042 ' new "netconf example rpc" -expecteof "$clixon_netconf -qf $cfg" 0 '0]]>]]>' '^042]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 '0]]>]]>' '^042]]>]]>$' # 2. Then error cases # +new "restconf empy rpc with null input" +ret=$(curl -is -X POST -d '{"example:input":null}' http://localhost/restconf/operations/example:empty) +expect="204 No Content" +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + +new2 "restconf empy rpc with input x" +expecteq "$(curl -s -X POST -d '{"example:input":{"x":0}}' http://localhost/restconf/operations/example:empty)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "x"},"error-severity": "error"}}} ' + new2 "restconf omit mandatory" -expecteq "$(curl -s -X POST -d '{"input":null}' http://localhost/restconf/operations/example:example)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "x"},"error-severity": "error","error-message": "Mandatory variable"}}} ' +expecteq "$(curl -s -X POST -d '{"example:input":null}' http://localhost/restconf/operations/example:example)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "x"},"error-severity": "error","error-message": "Mandatory variable"}}} ' new2 "restconf add extra" -expecteq "$(curl -s -X POST -d '{"input":{"x":"0","extra":"0"}}' http://localhost/restconf/operations/example:example)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}} ' +expecteq "$(curl -s -X POST -d '{"example:input":{"x":"0","extra":"0"}}' http://localhost/restconf/operations/example:example)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}} ' new2 "restconf wrong method" -expecteq "$(curl -s -X POST -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:wrong)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "wrong"},"error-severity": "error","error-message": "RPC not defined"}}} ' +expecteq "$(curl -s -X POST -d '{"example:input":{"x":"0"}}' http://localhost/restconf/operations/example:wrong)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "wrong"},"error-severity": "error","error-message": "RPC not defined"}}} ' new2 "restconf edit-config missing mandatory" -expecteq "$(curl -s -X POST -d '{"input":null}' http://localhost/restconf/operations/ietf-netconf:edit-config)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' +expecteq "$(curl -s -X POST -d '{"example:input":null}' http://localhost/restconf/operations/ietf-netconf:edit-config)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' new "netconf kill-session missing session-id mandatory" expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationmissing-elementsession-iderrorMandatory variable]]>]]>$' diff --git a/test/test_startup.sh b/test/test_startup.sh index d9ec5b07..ab0acb0f 100755 --- a/test/test_startup.sh +++ b/test/test_startup.sh @@ -41,7 +41,7 @@ run(){ dbdir=$dir/db cat < $dbdir - + run ex:eth @@ -53,7 +53,7 @@ EOF cat < $dbdir - + startup ex:eth @@ -65,7 +65,7 @@ EOF cat < $dir/config - + extra ex:eth @@ -104,8 +104,8 @@ EOF } run init '' -run none 'runex:ethtrue' -run running 'extraex:ethtrueloex:loopbacktruerunex:ethtrue' -run startup 'extraex:ethtrueloex:loopbacktruestartupex:ethtrue' +run none 'runex:ethtrue' +run running 'extraex:ethtrueloex:loopbacktruerunex:ethtrue' +run startup 'extraex:ethtrueloex:loopbacktruestartupex:ethtrue' rm -rf $dir diff --git a/test/test_stream.sh b/test/test_stream.sh index 87932744..2673b832 100755 --- a/test/test_stream.sh +++ b/test/test_stream.sh @@ -65,7 +65,7 @@ EOF # using reportingEntity (rfc5277) not reporting-entity (rfc8040) cat < $fyang module example { - namespace "http://example.com/event/1.0"; + namespace "urn:example:clixon"; prefix ex; organization "Example, Inc."; contact "support at example.com"; @@ -130,35 +130,35 @@ sleep $RCWAIT new "1. Netconf RFC5277 stream testing" # 1.1 Stream discovery new "netconf event stream discovery RFC5277 Sec 3.2.5" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'EXAMPLEExample event streamtrue]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'EXAMPLEExample event streamtrue]]>]]>' new "netconf event stream discovery RFC8040 Sec 6.2" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'EXAMPLEExample event streamtruexmlhttps://localhost/streams/EXAMPLE]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'EXAMPLEExample event streamtruexmlhttps://localhost/streams/EXAMPLE]]>]]>' # # 1.2 Netconf stream subscription new "netconf EXAMPLE subscription" -expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLE]]>]]>' '^]]>]]>20' $NCWAIT +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLE]]>]]>' '^]]>]]>20' $NCWAIT new "netconf subscription with empty startTime" -expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLE]]>]]>' '^]]>]]>20' $NCWAIT +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLE]]>]]>' '^]]>]]>20' $NCWAIT new "netconf EXAMPLE subscription with simple filter" -expectwait "$clixon_netconf -qf $cfg -y $fyang" "EXAMPLE]]>]]>" '^]]>]]>20' $NCWAIT +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLE]]>]]>' '^]]>]]>20' $NCWAIT new "netconf EXAMPLE subscription with filter classifier" -expectwait "$clixon_netconf -qf $cfg -y $fyang" "EXAMPLE]]>]]>" '^]]>]]>20' $NCWAIT +expectwait "$clixon_netconf -qf $cfg -y $fyang" "EXAMPLE]]>]]>" '^]]>]]>20' $NCWAIT new "netconf NONEXIST subscription" -expectwait "$clixon_netconf -qf $cfg -y $fyang" 'NONEXIST]]>]]>' '^applicationinvalid-valueerrorNo such stream]]>]]>$' $NCWAIT +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'NONEXIST]]>]]>' '^applicationinvalid-valueerrorNo such stream]]>]]>$' $NCWAIT new "netconf EXAMPLE subscription with wrong date" -expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLEkallekaka]]>]]>' '^applicationbad-elementstartTimeerrorInvalid time: kallekaka]]>]]>$' 0 +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLEkallekaka]]>]]>' '^applicationbad-elementstartTimeerrorInvalid time: kallekaka]]>]]>$' 0 #new "netconf EXAMPLE subscription with replay" #NOW=$(date +"%Y-%m-%dT%H:%M:%S") #sleep 10 -#expectwait "$clixon_netconf -qf $cfg -y $fyang" "EXAMPLE$NOW]]>]]>" '^]]>]]>20' 10 +#expectwait "$clixon_netconf -qf $cfg -y $fyang" "EXAMPLE$NOW]]>]]>" '^]]>]]>20' 10 sleep 2 # @@ -166,11 +166,11 @@ sleep 2 new "2. Restconf RFC8040 stream testing" # 2.1 Stream discovery new "restconf event stream discovery RFC8040 Sec 6.2" -expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams" 0 '{"streams": {"stream": \[{"name": "EXAMPLE","description": "Example event stream","replay-support": true,"access": \[{"encoding": "xml","location": "https://localhost/streams/EXAMPLE"}\]}\]}' +expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams" 0 '{"ietf-restconf-monitoring:streams": {"stream": \[{"name": "EXAMPLE","description": "Example event stream","replay-support": true,"access": \[{"encoding": "xml","location": "https://localhost/streams/EXAMPLE"}\]}\]}' sleep 2 new "restconf subscribe RFC8040 Sec 6.3, get location" -expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams/stream=EXAMPLE/access=xml/location" 0 '{"location": "https://localhost/streams/EXAMPLE"}' +expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams/stream=EXAMPLE/access=xml/location" 0 '{"ietf-restconf-monitoring:location": "https://localhost/streams/EXAMPLE"}' sleep 2 # Restconf stream subscription RFC8040 Sec 6.3 @@ -246,13 +246,12 @@ if [ $nr -lt 9 -o $nr -gt 14 ]; then err 10 "$nr" fi - # Try parallell # start background job curl -s -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" "http://localhost/streams/EXAMPLE" > /dev/null & PID=$! -new "Start subscription in parallell" +new "Start subscriptions in parallell" ret=$($UTIL -u http://localhost/streams/EXAMPLE -t 8) expect="data: ${DATE}T[0-9:.]*faultEthernet0major" diff --git a/test/test_type.sh b/test/test_type.sh index 1261d2ba..20f6f7aa 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -214,7 +214,7 @@ new "cli set transitive string error" expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c talle 9xx" 255 "^$" new "netconf set transitive string error" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "9xx]]>]]>" "^]]>]]>" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '9xx]]>]]>' "^]]>]]>" new "netconf validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" 'applicationbad-elementtalleerrorregexp match fail: "9xx" does not match \[a-z\]\[0-9\]\*]]>]]>$' @@ -241,7 +241,7 @@ new "cli set transitive union error int" expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c ulle 55" 255 "" new "netconf set transitive union error int" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "55]]>]]>" "^]]>]]>" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '55]]>]]>' "^]]>]]>" new "netconf validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationbad-elementulleerror'55' does not match enumeration]]>]]>$" @@ -276,7 +276,7 @@ new "netconf validate ok" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf set ab wrong" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "a.b& c.d]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'a.b& c.d]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^" @@ -298,7 +298,7 @@ expectfn "$clixon_cli -1f $cfg -l o -y $fyang set mbits create" 0 "^$" #expectfn "$clixon_cli -1f $cfg -l o -y $fyang set mbits \"create read\"" 0 "^$" new "netconf bits two values" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "create read]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'create read]]>]]>' "^]]>]]>$" new "cli bits validate" expectfn "$clixon_cli -1f $cfg -l o -y $fyang validate" 0 "^$" diff --git a/test/test_when_must.sh b/test/test_when_must.sh index 9f4c8d4b..82fc5647 100755 --- a/test/test_when_must.sh +++ b/test/test_when_must.sh @@ -106,16 +106,16 @@ if [ $BE -ne 0 ]; then fi new "when: add static route" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "staticr1]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'staticr1]]>]]>' "^]]>]]>$" new "when: validate ok" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "when: add direct route" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "directr2]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'directr2]]>]]>' "^]]>]]>$" new "when get config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^directr2staticr1]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^directr2staticr1]]>]]>$' new "when: validate fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorwhen xpath validation failed]]>]]>$" @@ -124,19 +124,19 @@ new "when: discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "must: add interface" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ethernet1500]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'ethernet1500]]>]]>' "^]]>]]>$" new "must: validate ok" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "must: add atm interface" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "atm32]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'atm32]]>]]>' "^]]>]]>$" new "must: atm validate fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorAn ATM MTU must be 64 .. 17966]]>]]>$" new "must: add eth interface" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ethernet989]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'ethernet989]]>]]>' "^]]>]]>$" new "must: eth validate fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorAn Ethernet MTU must be 1500]]>]]>" diff --git a/test/test_yang.sh b/test/test_yang.sh index 1d953dc5..46eb6234 100755 --- a/test/test_yang.sh +++ b/test/test_yang.sh @@ -160,13 +160,13 @@ new "cli defined extension" expectfn "$clixon_cli -1f $cfg -y $fyang show version" 0 "3." new "empty values in leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "a]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'a]]>]]>' "^]]>]]>$" new "empty values in leaf-list2" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" new "netconf get config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^a]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^a]]>]]>$' new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -182,7 +182,7 @@ new "netconf schema resource, RFC 7895" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'ietf-yang-types2013-07-15urn:ietf:params:xml:ns:yang:ietf-yang-typesimplement' new "netconf edit config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "125one]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '125one]]>]]>' "^]]>]]>$" new "netconf commit" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -192,33 +192,34 @@ new "netconf commit 2nd" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf get config xpath" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^125one]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^125one]]>]]>$' new "netconf edit leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "hejhopp]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'hejhopp]]>]]>' "^]]>]]>$" new "netconf get leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhopp]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^hejhopp]]>]]>$' new "netconf get leaf-list path" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhopp]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhopp]]>]]>$" new "netconf get (should be some)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^125one" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^125one' new "cli set leaf-list" expectfn "$clixon_cli -1f $cfg -y $fyang set x f e foo" 0 "" new "cli show leaf-list" expectfn "$clixon_cli -1f $cfg -y $fyang show xpath /x/f/e" 0 "foo" + new "netconf set state data (not allowed)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "42]]>]]>" "^protocolinvalid-value" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '42]]>]]>' '^protocolinvalid-valueerrorState data not allowed]]>]]>$' new "netconf set presence and not present" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" new "netconf get presence only" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^]]>]]>$' new "netconf get presence only" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" @@ -227,7 +228,7 @@ new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf anyxml" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" new "netconf validate anyxml" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -237,28 +238,28 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '111one]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '111one]]>]]>' "^]]>]]>$" new "netconf check add one 3-key" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111one]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111one]]>]]>' new "netconf add another (with same 1st key)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "121two]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '121two]]>]]>' "^]]>]]>$" new "netconf check add another" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111one121two]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111one121two]]>]]>' new "netconf replace first" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "111replace]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '111replace]]>]]>' "^]]>]]>$" new "netconf check replace" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111replace121two]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111replace121two]]>]]>' new "netconf delete first" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '111]]>]]>' "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '111]]>]]>' "^]]>]]>$" new "netconf check delete" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '121two]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '121two]]>]]>' # clear db for next test new "netconf delete candidate" @@ -268,10 +269,10 @@ new "netconf commit empty candidate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconfig config submodule" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "afoo]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'afoo]]>]]>' "^]]>]]>$" new "netconf submodule get config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^afoo]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^afoo]]>]]>$' new "netconf submodule validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" diff --git a/test/test_yang_load.sh b/test/test_yang_load.sh index 90fc10e4..4f168fca 100755 --- a/test/test_yang_load.sh +++ b/test/test_yang_load.sh @@ -20,7 +20,7 @@ cat < $fyang1 module $APPNAME{ prefix ex; revision 2018-12-02; - namespace "urn:example:example"; + namespace "urn:example:clixon"; leaf newex{ type string; } @@ -32,7 +32,7 @@ cat < $fyang2 module $APPNAME{ prefix ex; revision 2018-01-01; - namespace "urn:example:example"; + namespace "urn:example:clixon"; leaf oldex{ type string; } @@ -44,7 +44,7 @@ cat < $fyang3 module other{ prefix oth; revision 2018-01-01; - namespace "urn:example:example2"; + namespace "urn:example:clixon2"; leaf other{ type string; } @@ -85,13 +85,13 @@ if [ $BE -ne 0 ]; then fi new "1. Set newex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set oldex should fail (since oldex is in old revision and only the new is loaded)" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederrorValidation failed]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementoldexerrorUnassigned yang spec]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederrorValidation failed]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementothererrorUnassigned yang spec]]>]]>$' if [ $BE -eq 0 ]; then exit # BE @@ -137,13 +137,13 @@ if [ $? -ne 0 ]; then fi new "Set oldex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederrorValidation failed]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementnewexerrorUnassigned yang spec]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederrorValidation failed]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementothererrorUnassigned yang spec]]>]]>$' new "Kill backend" # Check if premature kill @@ -181,13 +181,13 @@ if [ $? -ne 0 ]; then fi new "Set newex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set oldex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementoldexerrorUnassigned yang spec]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementothererrorUnassigned yang spec]]>]]>$' new "Kill backend" # Check if premature kill @@ -226,13 +226,13 @@ if [ $? -ne 0 ]; then fi new "Set oldex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementnewexerrorUnassigned yang spec]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementothererrorUnassigned yang spec]]>]]>$' new "Kill backend" # Check if premature kill @@ -270,13 +270,13 @@ if [ $? -ne 0 ]; then fi new "Set newex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set oldex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementoldexerrorUnassigned yang spec]]>]]>$' new "Set other" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Kill backend" # Check if premature kill @@ -315,13 +315,13 @@ if [ $? -ne 0 ]; then fi new "Set oldex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementnewexerrorUnassigned yang spec]]>]]>' new "Set other" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Kill backend" # Check if premature kill @@ -362,13 +362,13 @@ if [ $? -ne 0 ]; then fi new "Set oldex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementnewexerrorUnassigned yang spec]]>]]>$' new "Set other" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Kill backend" # Check if premature kill @@ -408,13 +408,13 @@ if [ $? -ne 0 ]; then fi new "Set oldex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementnewexerrorUnassigned yang spec]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementothererrorUnassigned yang spec]]>]]>$' new "Kill backend" # Check if premature kill diff --git a/test/test_yang_namespace.sh b/test/test_yang_namespace.sh index 19447f80..43fb2893 100755 --- a/test/test_yang_namespace.sh +++ b/test/test_yang_namespace.sh @@ -1,11 +1,15 @@ #!/bin/bash -# Yang specifics: multi-keys and empty type + APPNAME=example -# include err() and new() functions and creates $dir +# test two modules example1 and example2 with overlapping statements x. +# x is leaf in example1 and list on example2. +# Test netconf and restconf +# BTW, this is not supported in generated CLI + . ./lib.sh cfg=$dir/conf_yang.xml -fyang=$dir/example.yang +fyang1=$dir/example1.yang fyang2=$dir/example2.yang # /usr/local/share/$APPNAME/yang @@ -18,17 +22,35 @@ cat < $cfg /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/cli $APPNAME + false /usr/local/var/$APPNAME/$APPNAME.sock /usr/local/var/$APPNAME/$APPNAME.pidfile 1 /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so + true true EOF + +cat < $fyang1 +module example1{ + yang-version 1.1; + prefix ex1; + namespace "urn:example:clixon1"; + import ietf-routing { + description "defines fib-route"; + prefix rt; + } + leaf x{ + type int32; + } +} +EOF + # For testing namespaces - -# x.y is different type. Here it is string whereas in fyang it is list. +# x.y is different type. Here it is string whereas in fyang1 it is list. # cat < $fyang2 module example2{ @@ -43,37 +65,6 @@ module example2{ } EOF -cat < $fyang -module example{ - yang-version 1.1; - prefix ex; - namespace "urn:example:clixon"; - import ietf-routing { - description "defines fib-route"; - prefix rt; - } - leaf x{ - type int32; - } - rpc client-rpc { - description "Example local client-side RPC that is processed by the - the netconf/restconf and not sent to the backend. - This is a clixon implementation detail: some rpc:s - are better processed by the client for API or perf reasons"; - input { - leaf request { - type string; - } - } - output { - leaf result{ - type string; - } - } - } -} -EOF - new "test params: -f $cfg" if [ $BE -ne 0 ]; then @@ -91,36 +82,57 @@ if [ $BE -ne 0 ]; then fi fi +new "kill old restconf daemon" +sudo pkill -u www-data clixon_restconf +new "start restconf daemon" +sudo su -c "$clixon_restconf -f $cfg -D $DBG" -s /bin/sh www-data & -new "netconf xmlns module ex" -expecteof "$clixon_netconf -qf $cfg" 0 '42]]>]]>' '^]]>]]>$' +new "netconf set x in example1" +expecteof "$clixon_netconf -qf $cfg" 0 '42]]>]]>' '^]]>]]>$' -new "netconf get config XXX xmlfn in return" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^42]]>]]>$" +new "netconf get config example1" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^42]]>]]>$' -new "netconf xmlns module ex2" +new "netconf set x in example2" expecteof "$clixon_netconf -qf $cfg" 0 '99]]>]]>' '^]]>]]>$' -new "netconf get config XXX xmlns" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^4299]]>]]>$" +new "netconf get config example1 and example2" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^4299]]>]]>$' new "netconf discard-changes" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang1" 0 "]]>]]>" "^]]>]]>$" -new "netconf xmlns:ex" -expecteof "$clixon_netconf -qf $cfg" 0 '4422]]>]]>' '^]]>]]>$' +new "restconf set x in example1" +expecteq "$(curl -s -X POST -d '{"example1:x":42}' http://localhost/restconf/data)" '' -new "netconf get config XXX xmlns:ex" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^4422]]>]]>$" +new2 "restconf get config example1" +expecteq "$(curl -s -X GET http://localhost/restconf/data/example1:x)" '{"example1:x": 42} + ' -new "netconf xmlns:ex2" -expecteof "$clixon_netconf -qf $cfg" 0 '9999]]>]]>' '^]]>]]>$' +new "restconf set x in example2" +expecteq "$(curl -s -X POST -d '{"example2:x":{"y":99}}' http://localhost/restconf/data)" '' -new "netconf get config XXX xmlns:ex2" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^44229999]]>]]>$" +# XXX GET ../example1:x is translated to select=/x which gets both example1&2 +#new "restconf get config example1" +#expecteq "$(curl -s -X GET http://localhost/restconf/data/example1:x)" '{"example1:x": 42} +# ' -# rpc +# XXX GET ../example2:x is translated to select=/x which gets both example1&2 +#new "restconf get config example2" +#expecteq "$(curl -s -X GET http://localhost/restconf/data/example2:x)" '{"example2:x": {"y":42}} +# ' + +new "restconf get config example1 and example2" +ret=$(curl -s -X GET http://localhost/restconf/data) +expect='"example1:x": 42,"example2:x": {"y": 99}' +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + +new "Kill restconf daemon" +sudo pkill -u www-data -f "/www-data/clixon_restconf" if [ $BE -eq 0 ]; then exit # BE diff --git a/yang/clixon-config@2018-10-21.yang b/yang/clixon-config@2018-10-21.yang index 162c3c0b..99d5b596 100644 --- a/yang/clixon-config@2018-10-21.yang +++ b/yang/clixon-config@2018-10-21.yang @@ -353,14 +353,19 @@ module clixon-config { Only works for Yang specified XML. If not set, all lists accessed via linear search."; } - leaf CLICON_XML_NS_ITERATE { + leaf CLICON_XML_NS_STRICT { type boolean; default true; description - "If set, iterate through modules to find the matching datanode + "If not set, make non-strict laze namespace checks, by iterating + through modules to find the matching datanode or rpc if no xmlns attribute specifies namespace. - This is loose semantics of finding namespaces. - And it is wrong, but is the way Clixon originally was written."; + This is lazy semantics of finding namespaces, which means you + do not need to explicitly give the namespace if the symbol exists + in some loaded module. + Example: is enough, instead of + But it is wrong, but is the way Clixon originally was written. + Strict semantics is the default."; } leaf CLICON_USE_STARTUP_CONFIG { type int32; diff --git a/yang/ietf-netconf-notification@2008-07-01.yang b/yang/ietf-netconf-notification@2008-07-01.yang index 978cdd4b..1db3ee3e 100644 --- a/yang/ietf-netconf-notification@2008-07-01.yang +++ b/yang/ietf-netconf-notification@2008-07-01.yang @@ -1,5 +1,6 @@ module ietf-netconf-notification { - namespace "urn:ietf:params:xml:ns:netconf:notification:1:0"; + /* namespace "urn:ietf:params:xml:ns:netconf:notification:1.0";*/ + namespace "urn:ietf:params:xml:ns:netmod:notification"; prefix "rcmon"; import ietf-yang-types { prefix yang; } From 0103d589945a9f7e325e8d500db5dce621f3074f Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 2 Jan 2019 15:48:30 +0100 Subject: [PATCH 05/17] 2019 --- .gitignore | 3 +-- LICENSE.md | 2 +- Makefile.in | 2 +- apps/Makefile.in | 2 +- apps/backend/Makefile.in | 2 +- apps/backend/backend_client.c | 2 +- apps/backend/backend_client.h | 2 +- apps/backend/backend_commit.c | 2 +- apps/backend/backend_commit.h | 2 +- apps/backend/backend_handle.h | 2 +- apps/backend/backend_main.c | 2 +- apps/backend/backend_plugin.c | 2 +- apps/backend/backend_plugin.h | 2 +- apps/backend/backend_socket.c | 2 +- apps/backend/backend_socket.h | 2 +- apps/backend/clixon_backend.h | 2 +- apps/backend/clixon_backend_handle.c | 2 +- apps/backend/clixon_backend_handle.h | 2 +- apps/backend/clixon_backend_transaction.c | 2 +- apps/backend/clixon_backend_transaction.h | 2 +- apps/cli/Makefile.in | 2 +- apps/cli/cli_common.c | 2 +- apps/cli/cli_common.h | 2 +- apps/cli/cli_generate.c | 2 +- apps/cli/cli_generate.h | 2 +- apps/cli/cli_handle.c | 2 +- apps/cli/cli_handle.h | 2 +- apps/cli/cli_main.c | 2 +- apps/cli/cli_plugin.c | 2 +- apps/cli/cli_plugin.h | 2 +- apps/cli/cli_show.c | 2 +- apps/cli/clixon_cli.h | 2 +- apps/cli/clixon_cli_api.h | 2 +- apps/netconf/Makefile.in | 2 +- apps/netconf/clixon_netconf.h | 2 +- apps/netconf/netconf_filter.c | 2 +- apps/netconf/netconf_filter.h | 2 +- apps/netconf/netconf_hello.c | 2 +- apps/netconf/netconf_hello.h | 2 +- apps/netconf/netconf_lib.c | 2 +- apps/netconf/netconf_lib.h | 2 +- apps/netconf/netconf_main.c | 2 +- apps/netconf/netconf_rpc.c | 2 +- apps/netconf/netconf_rpc.h | 2 +- apps/restconf/Makefile.in | 2 +- apps/restconf/clixon_restconf.h | 2 +- apps/restconf/restconf_lib.c | 2 +- apps/restconf/restconf_lib.h | 2 +- apps/restconf/restconf_main.c | 2 +- apps/restconf/restconf_methods.c | 2 +- apps/restconf/restconf_methods.h | 2 +- apps/restconf/restconf_stream.c | 2 +- apps/restconf/restconf_stream.h | 2 +- configure.ac | 2 +- datastore/Makefile.in | 2 +- datastore/datastore_client.c | 2 +- datastore/text/Makefile.in | 2 +- datastore/text/clixon_xmldb_text.c | 2 +- datastore/text/clixon_xmldb_text.h | 2 +- doc/Makefile.in | 2 +- docker/Dockerfile | 2 +- docker/Makefile.in | 2 +- etc/Makefile.in | 2 +- etc/clixonrc.in | 2 +- example/Dockerfile | 2 +- example/Makefile.in | 2 +- example/example_backend.c | 2 +- example/example_backend_nacm.c | 2 +- example/example_cli.c | 2 +- example/example_netconf.c | 2 +- example/example_restconf.c | 2 +- include/Makefile.in | 2 +- include/clixon_custom.h | 2 +- lib/Makefile.in | 2 +- lib/clixon/Makefile.in | 2 +- lib/clixon/clixon.h.in | 2 +- lib/clixon/clixon_err.h | 2 +- lib/clixon/clixon_event.h | 2 +- lib/clixon/clixon_file.h | 2 +- lib/clixon/clixon_handle.h | 2 +- lib/clixon/clixon_hash.h | 2 +- lib/clixon/clixon_json.h | 2 +- lib/clixon/clixon_log.h | 2 +- lib/clixon/clixon_nacm.h | 2 +- lib/clixon/clixon_netconf_lib.h | 2 +- lib/clixon/clixon_options.h | 2 +- lib/clixon/clixon_plugin.h | 2 +- lib/clixon/clixon_proto.h | 2 +- lib/clixon/clixon_proto_client.h | 2 +- lib/clixon/clixon_queue.h | 2 +- lib/clixon/clixon_sha1.h | 2 +- lib/clixon/clixon_sig.h | 2 +- lib/clixon/clixon_stream.h | 2 +- lib/clixon/clixon_string.h | 2 +- lib/clixon/clixon_xml.h | 2 +- lib/clixon/clixon_xml_db.h | 2 +- lib/clixon/clixon_xml_map.h | 2 +- lib/clixon/clixon_xml_sort.h | 2 +- lib/clixon/clixon_xpath.h | 2 +- lib/clixon/clixon_xpath_ctx.h | 2 +- lib/clixon/clixon_yang.h | 2 +- lib/clixon/clixon_yang_module.h | 2 +- lib/clixon/clixon_yang_type.h | 2 +- lib/src/Makefile.in | 2 +- lib/src/clixon_err.c | 2 +- lib/src/clixon_event.c | 2 +- lib/src/clixon_file.c | 2 +- lib/src/clixon_handle.c | 2 +- lib/src/clixon_hash.c | 2 +- lib/src/clixon_json.c | 2 +- lib/src/clixon_json_parse.h | 2 +- lib/src/clixon_json_parse.l | 2 +- lib/src/clixon_json_parse.y | 2 +- lib/src/clixon_log.c | 2 +- lib/src/clixon_nacm.c | 2 +- lib/src/clixon_netconf_lib.c | 2 +- lib/src/clixon_options.c | 2 +- lib/src/clixon_plugin.c | 2 +- lib/src/clixon_proto.c | 2 +- lib/src/clixon_proto_client.c | 2 +- lib/src/clixon_sig.c | 2 +- lib/src/clixon_stream.c | 2 +- lib/src/clixon_string.c | 2 +- lib/src/clixon_xml.c | 2 +- lib/src/clixon_xml_db.c | 2 +- lib/src/clixon_xml_map.c | 2 +- lib/src/clixon_xml_parse.h | 2 +- lib/src/clixon_xml_parse.l | 2 +- lib/src/clixon_xml_parse.y | 2 +- lib/src/clixon_xml_sort.c | 2 +- lib/src/clixon_xpath.c | 2 +- lib/src/clixon_xpath_ctx.c | 2 +- lib/src/clixon_xpath_parse.h | 2 +- lib/src/clixon_xpath_parse.l | 2 +- lib/src/clixon_xpath_parse.y | 2 +- lib/src/clixon_yang.c | 2 +- lib/src/clixon_yang_cardinality.c | 2 +- lib/src/clixon_yang_cardinality.h | 2 +- lib/src/clixon_yang_module.c | 2 +- lib/src/clixon_yang_parse.h | 2 +- lib/src/clixon_yang_parse.l | 2 +- lib/src/clixon_yang_parse.y | 2 +- lib/src/clixon_yang_type.c | 2 +- util/Makefile.in | 2 +- util/clixon_util_json.c | 2 +- util/clixon_util_stream.c | 2 +- util/clixon_util_xml.c | 2 +- util/clixon_util_xpath.c | 2 +- util/clixon_util_yang.c | 2 +- yang/Makefile.in | 2 +- yang/clixon-config@2018-10-21.yang | 2 +- 151 files changed, 151 insertions(+), 152 deletions(-) diff --git a/.gitignore b/.gitignore index cd4446bc..c04c1244 100644 --- a/.gitignore +++ b/.gitignore @@ -14,10 +14,9 @@ lib/Makefile lib/*/Makefile autom4te.cache/ -clixon.conf.cpp -clixon.mk config.log config.status +TAGS apps/backend/clixon_backend apps/backend/test diff --git a/LICENSE.md b/LICENSE.md index 84997252..af683735 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright 2009-2018 Olof Hagsand and Benny Holmgren +Copyright 2009-2019 Olof Hagsand and Benny Holmgren CLIXON is dual license. diff --git a/Makefile.in b/Makefile.in index 3d34933a..a1a9f1f0 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/apps/Makefile.in b/apps/Makefile.in index bfcc31cf..d8125aa0 100644 --- a/apps/Makefile.in +++ b/apps/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in index 4d57dfc8..b3700e33 100644 --- a/apps/backend/Makefile.in +++ b/apps/backend/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 3e1fa1ad..c9ea04ad 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/backend_client.h b/apps/backend/backend_client.h index 81620d4a..c9a1da11 100644 --- a/apps/backend/backend_client.h +++ b/apps/backend/backend_client.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 7c29131a..61e9a246 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/backend_commit.h b/apps/backend/backend_commit.h index 45b0f922..3af2461a 100644 --- a/apps/backend/backend_commit.h +++ b/apps/backend/backend_commit.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/backend_handle.h b/apps/backend/backend_handle.h index 8eb3e073..f2acea6f 100644 --- a/apps/backend/backend_handle.h +++ b/apps/backend/backend_handle.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index ba297852..77d791cf 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index 996a27cb..6218c0cd 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/backend_plugin.h b/apps/backend/backend_plugin.h index a2736b19..eb038319 100644 --- a/apps/backend/backend_plugin.h +++ b/apps/backend/backend_plugin.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/backend_socket.c b/apps/backend/backend_socket.c index ec32fb53..0d823ec8 100644 --- a/apps/backend/backend_socket.c +++ b/apps/backend/backend_socket.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/backend_socket.h b/apps/backend/backend_socket.h index df6ac149..8778be50 100644 --- a/apps/backend/backend_socket.h +++ b/apps/backend/backend_socket.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/clixon_backend.h b/apps/backend/clixon_backend.h index b3a75763..37e046b1 100644 --- a/apps/backend/clixon_backend.h +++ b/apps/backend/clixon_backend.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/clixon_backend_handle.c b/apps/backend/clixon_backend_handle.c index f4707de4..3ab58e9c 100644 --- a/apps/backend/clixon_backend_handle.c +++ b/apps/backend/clixon_backend_handle.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/clixon_backend_handle.h b/apps/backend/clixon_backend_handle.h index bdfbb425..491778aa 100644 --- a/apps/backend/clixon_backend_handle.h +++ b/apps/backend/clixon_backend_handle.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/clixon_backend_transaction.c b/apps/backend/clixon_backend_transaction.c index 173ba01c..727a7332 100644 --- a/apps/backend/clixon_backend_transaction.c +++ b/apps/backend/clixon_backend_transaction.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/clixon_backend_transaction.h b/apps/backend/clixon_backend_transaction.h index a290c5f1..447cd15f 100644 --- a/apps/backend/clixon_backend_transaction.h +++ b/apps/backend/clixon_backend_transaction.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/Makefile.in b/apps/cli/Makefile.in index a55918d9..2b75b479 100644 --- a/apps/cli/Makefile.in +++ b/apps/cli/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index f6796f58..ebab38a7 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/cli_common.h b/apps/cli/cli_common.h index 8e177e5e..4e29838f 100644 --- a/apps/cli/cli_common.h +++ b/apps/cli/cli_common.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 022e41b8..b25067c4 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/cli_generate.h b/apps/cli/cli_generate.h index 371063ba..d049d600 100644 --- a/apps/cli/cli_generate.h +++ b/apps/cli/cli_generate.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/cli_handle.c b/apps/cli/cli_handle.c index 58e03948..34a92c4d 100644 --- a/apps/cli/cli_handle.c +++ b/apps/cli/cli_handle.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/cli_handle.h b/apps/cli/cli_handle.h index bbf66742..c7fd728b 100644 --- a/apps/cli/cli_handle.h +++ b/apps/cli/cli_handle.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index b22e1c9f..e6d7925a 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c index 633c46ae..4f17f948 100644 --- a/apps/cli/cli_plugin.c +++ b/apps/cli/cli_plugin.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/cli_plugin.h b/apps/cli/cli_plugin.h index 2df7fb47..75aa99c2 100644 --- a/apps/cli/cli_plugin.h +++ b/apps/cli/cli_plugin.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index ec39ac4e..0fa9fa56 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/clixon_cli.h b/apps/cli/clixon_cli.h index c41d7185..aaebfb19 100644 --- a/apps/cli/clixon_cli.h +++ b/apps/cli/clixon_cli.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/clixon_cli_api.h b/apps/cli/clixon_cli_api.h index 3507e31f..d2926029 100644 --- a/apps/cli/clixon_cli_api.h +++ b/apps/cli/clixon_cli_api.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/netconf/Makefile.in b/apps/netconf/Makefile.in index 81d86577..ee2d0082 100644 --- a/apps/netconf/Makefile.in +++ b/apps/netconf/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/apps/netconf/clixon_netconf.h b/apps/netconf/clixon_netconf.h index 9fed0a1a..470474f1 100644 --- a/apps/netconf/clixon_netconf.h +++ b/apps/netconf/clixon_netconf.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/netconf/netconf_filter.c b/apps/netconf/netconf_filter.c index 89b698e9..8451c33b 100644 --- a/apps/netconf/netconf_filter.c +++ b/apps/netconf/netconf_filter.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/netconf/netconf_filter.h b/apps/netconf/netconf_filter.h index ed5e3d98..ce7d76e6 100644 --- a/apps/netconf/netconf_filter.h +++ b/apps/netconf/netconf_filter.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/netconf/netconf_hello.c b/apps/netconf/netconf_hello.c index 1be1211c..9c4b07ba 100644 --- a/apps/netconf/netconf_hello.c +++ b/apps/netconf/netconf_hello.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/netconf/netconf_hello.h b/apps/netconf/netconf_hello.h index 4f877b32..2ccec1b8 100644 --- a/apps/netconf/netconf_hello.h +++ b/apps/netconf/netconf_hello.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/netconf/netconf_lib.c b/apps/netconf/netconf_lib.c index 50723de4..0a6b869b 100644 --- a/apps/netconf/netconf_lib.c +++ b/apps/netconf/netconf_lib.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/netconf/netconf_lib.h b/apps/netconf/netconf_lib.h index 6d2de111..d243ac6a 100644 --- a/apps/netconf/netconf_lib.h +++ b/apps/netconf/netconf_lib.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index 4cdef1a0..e60a5d08 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 7945110e..f2bbb605 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/netconf/netconf_rpc.h b/apps/netconf/netconf_rpc.h index 3ce6a615..4a20397f 100644 --- a/apps/netconf/netconf_rpc.h +++ b/apps/netconf/netconf_rpc.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/restconf/Makefile.in b/apps/restconf/Makefile.in index 32366f89..768a261d 100644 --- a/apps/restconf/Makefile.in +++ b/apps/restconf/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/apps/restconf/clixon_restconf.h b/apps/restconf/clixon_restconf.h index 2c79c3cc..a8625e9f 100644 --- a/apps/restconf/clixon_restconf.h +++ b/apps/restconf/clixon_restconf.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 062dff65..500a07c3 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index e05eb054..8c478370 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index d5067c4f..e6234884 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index b2910a1b..8f3cb4a5 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/restconf/restconf_methods.h b/apps/restconf/restconf_methods.h index 11daaa26..42525946 100644 --- a/apps/restconf/restconf_methods.h +++ b/apps/restconf/restconf_methods.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/restconf/restconf_stream.c b/apps/restconf/restconf_stream.c index ec20f373..667db0ad 100644 --- a/apps/restconf/restconf_stream.c +++ b/apps/restconf/restconf_stream.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/restconf/restconf_stream.h b/apps/restconf/restconf_stream.h index 480832f1..0cea952f 100644 --- a/apps/restconf/restconf_stream.h +++ b/apps/restconf/restconf_stream.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/configure.ac b/configure.ac index ebdccaf6..94710961 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/datastore/Makefile.in b/datastore/Makefile.in index 6943dec5..5adde268 100644 --- a/datastore/Makefile.in +++ b/datastore/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/datastore/datastore_client.c b/datastore/datastore_client.c index b2a20029..0bbe5a3b 100644 --- a/datastore/datastore_client.c +++ b/datastore/datastore_client.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/datastore/text/Makefile.in b/datastore/text/Makefile.in index 23c2a7bf..9eadb121 100644 --- a/datastore/text/Makefile.in +++ b/datastore/text/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index 645fef97..d4160253 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/datastore/text/clixon_xmldb_text.h b/datastore/text/clixon_xmldb_text.h index 19e9351d..9bb46e42 100644 --- a/datastore/text/clixon_xmldb_text.h +++ b/datastore/text/clixon_xmldb_text.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/doc/Makefile.in b/doc/Makefile.in index ad0f4203..f67f183d 100644 --- a/doc/Makefile.in +++ b/doc/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/docker/Dockerfile b/docker/Dockerfile index b1a71ce3..a15c874b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/docker/Makefile.in b/docker/Makefile.in index c18b9fad..375fa22d 100644 --- a/docker/Makefile.in +++ b/docker/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/etc/Makefile.in b/etc/Makefile.in index 263840df..09c671af 100644 --- a/etc/Makefile.in +++ b/etc/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/etc/clixonrc.in b/etc/clixonrc.in index e06018c6..c2a5cde3 100644 --- a/etc/clixonrc.in +++ b/etc/clixonrc.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/example/Dockerfile b/example/Dockerfile index 08314bf8..27cfb737 100644 --- a/example/Dockerfile +++ b/example/Dockerfile @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/example/Makefile.in b/example/Makefile.in index bbbd9070..8e18d3ea 100644 --- a/example/Makefile.in +++ b/example/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/example/example_backend.c b/example/example_backend.c index ae1f51d4..7c09ee3d 100644 --- a/example/example_backend.c +++ b/example/example_backend.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/example/example_backend_nacm.c b/example/example_backend_nacm.c index 3814a975..608916a8 100644 --- a/example/example_backend_nacm.c +++ b/example/example_backend_nacm.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/example/example_cli.c b/example/example_cli.c index 01a375a3..3aaf9259 100644 --- a/example/example_cli.c +++ b/example/example_cli.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/example/example_netconf.c b/example/example_netconf.c index b70128de..742b1d98 100644 --- a/example/example_netconf.c +++ b/example/example_netconf.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/example/example_restconf.c b/example/example_restconf.c index ec0e8d27..e49a48f4 100644 --- a/example/example_restconf.c +++ b/example/example_restconf.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/include/Makefile.in b/include/Makefile.in index 75aa04a1..d55e6e97 100644 --- a/include/Makefile.in +++ b/include/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/include/clixon_custom.h b/include/clixon_custom.h index 8b7c5ae6..7a6c342a 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -1,7 +1,7 @@ /* ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/Makefile.in b/lib/Makefile.in index d34e81ac..a0f09147 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/lib/clixon/Makefile.in b/lib/clixon/Makefile.in index e455a860..eee5ce91 100644 --- a/lib/clixon/Makefile.in +++ b/lib/clixon/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/lib/clixon/clixon.h.in b/lib/clixon/clixon.h.in index 2fc6c50d..6615433c 100644 --- a/lib/clixon/clixon.h.in +++ b/lib/clixon/clixon.h.in @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_err.h b/lib/clixon/clixon_err.h index b5961453..63fff1fe 100644 --- a/lib/clixon/clixon_err.h +++ b/lib/clixon/clixon_err.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_event.h b/lib/clixon/clixon_event.h index 99b598e2..c2ef6edc 100644 --- a/lib/clixon/clixon_event.h +++ b/lib/clixon/clixon_event.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_file.h b/lib/clixon/clixon_file.h index 3e34b6ce..44915abd 100644 --- a/lib/clixon/clixon_file.h +++ b/lib/clixon/clixon_file.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_handle.h b/lib/clixon/clixon_handle.h index 711e6cb8..944f5911 100644 --- a/lib/clixon/clixon_handle.h +++ b/lib/clixon/clixon_handle.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_hash.h b/lib/clixon/clixon_hash.h index 0adbb047..0cbb8129 100644 --- a/lib/clixon/clixon_hash.h +++ b/lib/clixon/clixon_hash.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_json.h b/lib/clixon/clixon_json.h index 0a644680..0a4af828 100644 --- a/lib/clixon/clixon_json.h +++ b/lib/clixon/clixon_json.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_log.h b/lib/clixon/clixon_log.h index 7db79142..a9835488 100644 --- a/lib/clixon/clixon_log.h +++ b/lib/clixon/clixon_log.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_nacm.h b/lib/clixon/clixon_nacm.h index 978b5486..cdae13bb 100644 --- a/lib/clixon/clixon_nacm.h +++ b/lib/clixon/clixon_nacm.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index a615c663..2bb4e8f2 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index a849c6cd..af92305c 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index cd6038aa..09703d90 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_proto.h b/lib/clixon/clixon_proto.h index e3290c3f..3d9e6603 100644 --- a/lib/clixon/clixon_proto.h +++ b/lib/clixon/clixon_proto.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_proto_client.h b/lib/clixon/clixon_proto_client.h index ac917d77..07d808b5 100644 --- a/lib/clixon/clixon_proto_client.h +++ b/lib/clixon/clixon_proto_client.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_queue.h b/lib/clixon/clixon_queue.h index 70127756..ee277134 100644 --- a/lib/clixon/clixon_queue.h +++ b/lib/clixon/clixon_queue.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_sha1.h b/lib/clixon/clixon_sha1.h index 6f788e14..fbf9bfc5 100644 --- a/lib/clixon/clixon_sha1.h +++ b/lib/clixon/clixon_sha1.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_sig.h b/lib/clixon/clixon_sig.h index ac03a908..51c29341 100644 --- a/lib/clixon/clixon_sig.h +++ b/lib/clixon/clixon_sig.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_stream.h b/lib/clixon/clixon_stream.h index a1870395..ae09a8ab 100644 --- a/lib/clixon/clixon_stream.h +++ b/lib/clixon/clixon_stream.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_string.h b/lib/clixon/clixon_string.h index dd2eb290..1b2b20fc 100644 --- a/lib/clixon/clixon_string.h +++ b/lib/clixon/clixon_string.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index 2117bfef..be3e2352 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_xml_db.h b/lib/clixon/clixon_xml_db.h index 58607396..a79349d1 100644 --- a/lib/clixon/clixon_xml_db.h +++ b/lib/clixon/clixon_xml_db.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 9eba5bfd..5148589a 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_xml_sort.h b/lib/clixon/clixon_xml_sort.h index c4ffeab3..2a58472f 100644 --- a/lib/clixon/clixon_xml_sort.h +++ b/lib/clixon/clixon_xml_sort.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_xpath.h b/lib/clixon/clixon_xpath.h index ce62fc23..d0d6a360 100644 --- a/lib/clixon/clixon_xpath.h +++ b/lib/clixon/clixon_xpath.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_xpath_ctx.h b/lib/clixon/clixon_xpath_ctx.h index 03854495..0a1bb122 100644 --- a/lib/clixon/clixon_xpath_ctx.h +++ b/lib/clixon/clixon_xpath_ctx.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index a9291583..fb727dfd 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_yang_module.h b/lib/clixon/clixon_yang_module.h index 0ca4760c..2a26b7ba 100644 --- a/lib/clixon/clixon_yang_module.h +++ b/lib/clixon/clixon_yang_module.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_yang_type.h b/lib/clixon/clixon_yang_type.h index 61b581af..8fecdd5f 100644 --- a/lib/clixon/clixon_yang_type.h +++ b/lib/clixon/clixon_yang_type.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index ce0c0587..fc8fa3e4 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/lib/src/clixon_err.c b/lib/src/clixon_err.c index 6ec992c6..b4f07fbf 100644 --- a/lib/src/clixon_err.c +++ b/lib/src/clixon_err.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_event.c b/lib/src/clixon_event.c index d15e12c5..3085f89c 100644 --- a/lib/src/clixon_event.c +++ b/lib/src/clixon_event.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_file.c b/lib/src/clixon_file.c index 22998553..73a2b894 100644 --- a/lib/src/clixon_file.c +++ b/lib/src/clixon_file.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_handle.c b/lib/src/clixon_handle.c index dcb6da5c..a89d81fd 100644 --- a/lib/src/clixon_handle.c +++ b/lib/src/clixon_handle.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_hash.c b/lib/src/clixon_hash.c index aa397d4d..4769abfa 100644 --- a/lib/src/clixon_hash.c +++ b/lib/src/clixon_hash.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index 1cd99af1..f6f8ff23 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_json_parse.h b/lib/src/clixon_json_parse.h index 8196e942..0f088a32 100644 --- a/lib/src/clixon_json_parse.h +++ b/lib/src/clixon_json_parse.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_json_parse.l b/lib/src/clixon_json_parse.l index 3763d7f1..6d720140 100644 --- a/lib/src/clixon_json_parse.l +++ b/lib/src/clixon_json_parse.l @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_json_parse.y b/lib/src/clixon_json_parse.y index 1cf6a583..a6533a06 100644 --- a/lib/src/clixon_json_parse.y +++ b/lib/src/clixon_json_parse.y @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_log.c b/lib/src/clixon_log.c index 87e0c8de..4c8641c3 100644 --- a/lib/src/clixon_log.c +++ b/lib/src/clixon_log.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_nacm.c b/lib/src/clixon_nacm.c index bf60b865..8efa4421 100644 --- a/lib/src/clixon_nacm.c +++ b/lib/src/clixon_nacm.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index b8981bbc..cfa93882 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index e6410ad8..449b33ae 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c index 03ed2b29..f0a28a6f 100644 --- a/lib/src/clixon_plugin.c +++ b/lib/src/clixon_plugin.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_proto.c b/lib/src/clixon_proto.c index 17b09614..30da4e59 100644 --- a/lib/src/clixon_proto.c +++ b/lib/src/clixon_proto.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 396a0665..0dbb76d1 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_sig.c b/lib/src/clixon_sig.c index c1374876..d7c27335 100644 --- a/lib/src/clixon_sig.c +++ b/lib/src/clixon_sig.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_stream.c b/lib/src/clixon_stream.c index ae454256..6bad4671 100644 --- a/lib/src/clixon_stream.c +++ b/lib/src/clixon_stream.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_string.c b/lib/src/clixon_string.c index a7c05f39..b3e5e588 100644 --- a/lib/src/clixon_string.c +++ b/lib/src/clixon_string.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 6849e94d..d4f15a13 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index 32aab817..8fb5da70 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 5d210644..5b4f513d 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xml_parse.h b/lib/src/clixon_xml_parse.h index 3122e8f0..35ee2dee 100644 --- a/lib/src/clixon_xml_parse.h +++ b/lib/src/clixon_xml_parse.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xml_parse.l b/lib/src/clixon_xml_parse.l index 39173634..812b246c 100644 --- a/lib/src/clixon_xml_parse.l +++ b/lib/src/clixon_xml_parse.l @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y index d6a1200f..9e2eafaa 100644 --- a/lib/src/clixon_xml_parse.y +++ b/lib/src/clixon_xml_parse.y @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index 265d82a5..d227a961 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xpath.c b/lib/src/clixon_xpath.c index 2fec113a..b814ffc4 100644 --- a/lib/src/clixon_xpath.c +++ b/lib/src/clixon_xpath.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xpath_ctx.c b/lib/src/clixon_xpath_ctx.c index 721df45c..a50ff5c2 100644 --- a/lib/src/clixon_xpath_ctx.c +++ b/lib/src/clixon_xpath_ctx.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xpath_parse.h b/lib/src/clixon_xpath_parse.h index 1affce3c..6ba57672 100644 --- a/lib/src/clixon_xpath_parse.h +++ b/lib/src/clixon_xpath_parse.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xpath_parse.l b/lib/src/clixon_xpath_parse.l index 5ac54644..a886dd70 100644 --- a/lib/src/clixon_xpath_parse.l +++ b/lib/src/clixon_xpath_parse.l @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xpath_parse.y b/lib/src/clixon_xpath_parse.y index 4e055ca7..b121cc2c 100644 --- a/lib/src/clixon_xpath_parse.y +++ b/lib/src/clixon_xpath_parse.y @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 2298235a..36404682 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_yang_cardinality.c b/lib/src/clixon_yang_cardinality.c index 9c26fe92..638a077b 100644 --- a/lib/src/clixon_yang_cardinality.c +++ b/lib/src/clixon_yang_cardinality.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_yang_cardinality.h b/lib/src/clixon_yang_cardinality.h index eb71e8ea..815d8d56 100644 --- a/lib/src/clixon_yang_cardinality.h +++ b/lib/src/clixon_yang_cardinality.h @@ -1,7 +1,7 @@ /* ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c index c0b1e408..500aaf89 100644 --- a/lib/src/clixon_yang_module.c +++ b/lib/src/clixon_yang_module.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_yang_parse.h b/lib/src/clixon_yang_parse.h index baa136df..d85e8f6b 100644 --- a/lib/src/clixon_yang_parse.h +++ b/lib/src/clixon_yang_parse.h @@ -5,7 +5,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_yang_parse.l b/lib/src/clixon_yang_parse.l index 58715c0c..905eb388 100644 --- a/lib/src/clixon_yang_parse.l +++ b/lib/src/clixon_yang_parse.l @@ -1,7 +1,7 @@ /* ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y index 7bd42cc1..9ffac45e 100644 --- a/lib/src/clixon_yang_parse.y +++ b/lib/src/clixon_yang_parse.y @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c index 788af4f7..d043abff 100644 --- a/lib/src/clixon_yang_type.c +++ b/lib/src/clixon_yang_type.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/util/Makefile.in b/util/Makefile.in index 5095b612..657cd3db 100644 --- a/util/Makefile.in +++ b/util/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/util/clixon_util_json.c b/util/clixon_util_json.c index dd16e0e0..b60e2299 100644 --- a/util/clixon_util_json.c +++ b/util/clixon_util_json.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/util/clixon_util_stream.c b/util/clixon_util_stream.c index 6f45de57..3530514f 100644 --- a/util/clixon_util_stream.c +++ b/util/clixon_util_stream.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/util/clixon_util_xml.c b/util/clixon_util_xml.c index 88d2ab53..8b3e3fa6 100644 --- a/util/clixon_util_xml.c +++ b/util/clixon_util_xml.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/util/clixon_util_xpath.c b/util/clixon_util_xpath.c index 81b33361..b711b072 100644 --- a/util/clixon_util_xpath.c +++ b/util/clixon_util_xpath.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/util/clixon_util_yang.c b/util/clixon_util_yang.c index 3615fd27..9f3b0e15 100644 --- a/util/clixon_util_yang.c +++ b/util/clixon_util_yang.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/yang/Makefile.in b/yang/Makefile.in index b595b5ea..fb32b74d 100644 --- a/yang/Makefile.in +++ b/yang/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/yang/clixon-config@2018-10-21.yang b/yang/clixon-config@2018-10-21.yang index 99d5b596..d6b8c2c1 100644 --- a/yang/clixon-config@2018-10-21.yang +++ b/yang/clixon-config@2018-10-21.yang @@ -12,7 +12,7 @@ module clixon-config { description "Clixon configuration file ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON From 32ea957a524c36828b879d3498741baae00f20c0 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 2 Jan 2019 17:43:58 +0100 Subject: [PATCH 06/17] CLICON_XML_SORT option (in clixon-config.yang) has been removed and set to true permanently since setting it to false is obsolete. --- CHANGELOG.md | 1 + apps/backend/backend_client.c | 2 +- apps/backend/backend_main.c | 2 - datastore/text/clixon_xmldb_text.c | 29 ++---------- example/example_cli.c | 2 +- lib/clixon/clixon_options.h | 3 -- lib/clixon/clixon_xml_map.h | 1 - lib/clixon/clixon_xml_sort.h | 9 +--- lib/src/clixon_options.c | 7 +-- lib/src/clixon_xml_map.c | 74 ++---------------------------- lib/src/clixon_xml_sort.c | 24 ++-------- test/test_cli.sh | 8 +++- test/test_nacm_ext.sh | 2 +- yang/clixon-config@2018-10-21.yang | 9 ---- 14 files changed, 28 insertions(+), 145 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad2ebd7a..cbc70587 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,7 @@ * CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded. ### API changes on existing features (you may need to change your code) +* CLICON_XML_SORT option (in clixon-config.yang) has been removed and set to true permanently since setting it to false is obsolete. * Strict namespace setting can be a problem when upgrading existing database files, such as startup-db or persistent running-db, or any other saved XML file. * For backward compatibility, load of startup and running set CLICON_XML_NS_STRICT to false temporarily. * Removed `delete-config` support for candidate db since it is not supported in RFC6241. diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index c9ea04ad..6a8b5943 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -482,7 +482,7 @@ from_client_edit_config(clicon_handle h, /* Cant do this earlier since we dont have a yang spec to * the upper part of the tree, until we get the "config" tree. */ - if (clicon_xml_sort(h) && xml_apply0(xc, CX_ELMNT, xml_sort, NULL) < 0) + if (xml_apply0(xc, CX_ELMNT, xml_sort, NULL) < 0) goto done; if ((ret = xmldb_put(h, target, operation, xc, cbret)) < 0){ clicon_debug(1, "%s ERROR PUT", __FUNCTION__); diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 77d791cf..8e05c175 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -832,8 +832,6 @@ main(int argc, goto done; if (xmldb_setopt(h, "pretty", (void*)(intptr_t)clicon_option_bool(h, "CLICON_XMLDB_PRETTY")) < 0) goto done; - if (xmldb_setopt(h, "sort", (void*)(intptr_t)clicon_option_bool(h, "CLICON_XML_SORT")) < 0) - goto done; /* Startup mode needs to be defined, */ startup_mode = clicon_startup_mode(h); if (startup_mode == -1){ diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index d4160253..42210170 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -88,8 +88,6 @@ struct text_handle { Assumes single backend*/ char *th_format; /* Datastroe format: xml / json */ int th_pretty; /* Store xml/json pretty-printed. */ - int th_sort; /* Sort XML lists and leaf-lists alphabetically - (unless ordered-by user) */ }; /* Struct per database in hash */ @@ -241,8 +239,6 @@ text_getopt(xmldb_handle xh, *value = th->th_format; else if (strcmp(optname, "pretty") == 0) *value = &th->th_pretty; - else if (strcmp(optname, "sort") == 0) - *value = &th->th_sort; else{ clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname); goto done; @@ -292,9 +288,6 @@ text_setopt(xmldb_handle xh, else if (strcmp(optname, "pretty") == 0){ th->th_pretty = (intptr_t)value; } - else if (strcmp(optname, "sort") == 0){ - th->th_sort = (intptr_t)value; - } else{ clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname); goto done; @@ -551,14 +544,8 @@ text_get(xmldb_handle xh, /* Add default values (if not set) */ if (xml_apply(xt, CX_ELMNT, xml_default, NULL) < 0) goto done; - /* Order XML children according to YANG. - * XXX: should this be !th->th_sort? - */ - if (!th->th_sort) - if (xml_apply(xt, CX_ELMNT, xml_order, NULL) < 0) - goto done; #if 1 /* debug */ - if (th->th_sort && xml_apply0(xt, -1, xml_sort_verify, NULL) < 0) + if (xml_apply0(xt, -1, xml_sort_verify, NULL) < 0) clicon_log(LOG_NOTICE, "%s: sort verify failed #2", __FUNCTION__); #endif if (debug>1) @@ -741,7 +728,7 @@ text_modify(struct text_handle *th, } /* See if there is a corresponding node in the base tree */ x0c = NULL; - if (match_base_child(x0, x1c, &x0c, th->th_sort, yc) < 0) + if (match_base_child(x0, x1c, &x0c, yc) < 0) goto done; x0vec[i++] = x0c; } @@ -865,7 +852,7 @@ text_modify_top(struct text_handle *th, goto ok; } /* See if there is a corresponding node in the base tree */ - if (match_base_child(x0, x1c, &x0c, th->th_sort, yc) < 0) + if (match_base_child(x0, x1c, &x0c, yc) < 0) goto done; if (text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret) < 0) goto done; @@ -996,14 +983,8 @@ text_put(xmldb_handle xh, xml_name(x0)); goto done; } -#if 0 - /* Add yang specification backpointer to all XML nodes - * This is already done in from_client_edit_config() */ - if (xml_apply(x1, CX_ELMNT, xml_spec_populate, yspec) < 0) - goto done; -#endif #if 0 /* debug */ - if (th->th_sort && xml_apply0(x1, -1, xml_sort_verify, NULL) < 0) + if (xml_apply0(x1, -1, xml_sort_verify, NULL) < 0) clicon_log(LOG_NOTICE, "%s: verify failed #1", __FUNCTION__); #endif /* @@ -1029,7 +1010,7 @@ text_put(xmldb_handle xh, if (xml_tree_prune_flagged(x0, XML_FLAG_MARK, 1) < 0) goto done; #if 0 /* debug */ - if (th->th_sort && xml_apply0(x0, -1, xml_sort_verify, NULL) < 0) + if (xml_apply0(x0, -1, xml_sort_verify, NULL) < 0) clicon_log(LOG_NOTICE, "%s: verify failed #3", __FUNCTION__); #endif /* Write back to datastore cache if first time */ diff --git a/example/example_cli.c b/example/example_cli.c index 3aaf9259..bf24064d 100644 --- a/example/example_cli.c +++ b/example/example_cli.c @@ -110,7 +110,7 @@ fib_route_rpc(clicon_handle h, goto done; } /* Print result */ - xml2txt(stdout, xml_child_i(xret, 0), 1); + xml2txt(stdout, xml_child_i(xret, 0), 0); retval = 0; done: if (xret) diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index af92305c..04aae2e6 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -153,9 +153,6 @@ static inline char *clicon_xmldb_dir(clicon_handle h){ static inline char *clicon_xmldb_plugin(clicon_handle h){ return clicon_option_str(h, "CLICON_XMLDB_PLUGIN"); } -static inline int clicon_xml_sort(clicon_handle h){ - return clicon_option_bool(h, "CLICON_XML_SORT"); -} /*-- Specific option access functions for YANG options w type conversion--*/ int clicon_cli_genmodel(clicon_handle h); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 5148589a..2fa2afeb 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -62,7 +62,6 @@ int api_path_fmt2xpath(char *api_path_fmt, cvec *cvv, char **xpath); int xml_tree_prune_flagged_sub(cxobj *xt, int flag, int test, int *upmark); int xml_tree_prune_flagged(cxobj *xt, int flag, int test); int xml_default(cxobj *x, void *arg); -int xml_order(cxobj *x, void *arg); int xml_sanity(cxobj *x, void *arg); int xml_non_config_data(cxobj *xt, void *arg); int xml_spec_populate_rpc(clicon_handle h, cxobj *x, yang_spec *yspec); diff --git a/lib/clixon/clixon_xml_sort.h b/lib/clixon/clixon_xml_sort.h index 2a58472f..83f94f44 100644 --- a/lib/clixon/clixon_xml_sort.h +++ b/lib/clixon/clixon_xml_sort.h @@ -36,13 +36,6 @@ #ifndef _CLIXON_XML_SORT_H #define _CLIXON_XML_SORT_H -/* Sort and binary search of XML children - * XXX This variable is a kludge since low-level functions xml_merge/xml_diff calls - * match_base_child without handle - * @see clicon_xml_sort - */ -extern int xml_child_sort; - /* * Prototypes */ @@ -55,6 +48,6 @@ int xml_insert_pos(cxobj *x0, char *name, int yangi, enum rfc_6020 keyword, int upper); cxobj *xml_match(cxobj *x0, char *name, enum rfc_6020 keyword, int keynr, char **keyvec, char **keyval); int xml_sort_verify(cxobj *x, void *arg); -int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, int xml_sort, yang_stmt *yc); +int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, yang_stmt *yc); #endif /* _CLIXON_XML_SORT_H */ diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 449b33ae..4fdb1dce 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -207,6 +207,8 @@ parse_configfile(clicon_handle h, *xconfig = xt; xt = NULL; done: + if (cbret) + cbuf_free(cbret); if (xt) xml_free(xt); if (f) @@ -275,11 +277,6 @@ clicon_options_main(clicon_handle h, goto done; /* Set clixon_conf pointer to handle */ clicon_conf_xml_set(h, xconfig); - /* Specific option handling */ - if (clicon_option_bool(h, "CLICON_XML_SORT") == 1) - xml_child_sort = 1; - else - xml_child_sort = 0; /* XXX Kludge to low-level functions to search for xml in all yang modules */ _CLICON_XML_NS_STRICT = clicon_option_bool(h, "CLICON_XML_NS_STRICT"); retval = 0; diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 5b4f513d..d3585ee4 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -988,8 +988,7 @@ xml_diff1(yang_stmt *ys, clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x1c)); goto done; } - /* XXX xml_child_sort is global */ - if (match_base_child(x2, x1c, &x2c, xml_child_sort, yc) < 0) + if (match_base_child(x2, x1c, &x2c, yc) < 0) goto done; if (x2c == NULL){ if (cxvec_append(x1c, x1vec, x1veclen) < 0) @@ -1025,8 +1024,7 @@ xml_diff1(yang_stmt *ys, clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x2c)); goto done; } - /* XXX xml_child_sort is global */ - if (match_base_child(x1, x2c, &x1c, xml_child_sort, yc) < 0) + if (match_base_child(x1, x2c, &x1c, yc) < 0) goto done; if (x1c == NULL) if (cxvec_append(x2c, x2vec, x2veclen) < 0) @@ -1550,70 +1548,6 @@ xml_default(cxobj *xt, return retval; } -/*! Order XML children according to YANG - * @param[in] xt XML top of tree - * @param[in] arg Dummy (so it can be called from xml_apply) - * @see xml_sort XXX: how do they relate? - * Called from text_get *only* - */ -int -xml_order(cxobj *xt, - void *arg) -{ - int retval = -1; - yang_stmt *y; - yang_stmt *yc; - int i; - int j0; - int j; - cxobj *xc; - cxobj *xj; - char *yname; /* yang child name */ - char *xname; /* xml child name */ - - if ((y = (yang_stmt*)xml_spec(xt)) == NULL){ - retval = 0; - goto done; - } - j0 = 0; - /* Go through yang node's children and ensure that the - * xml children follow this order. - * Do not order the list or leaf-list children (have same name). - */ - for (i=0; iys_len; i++){ - yc = y->ys_stmt[i]; - if (!yang_datanode(yc)) - continue; - yname = yc->ys_argument; - /* First go thru xml children with same name in rest of list */ - for (; j0ys_keyword, keynr, keyvec, keyval); + if (xml_child_nr(x0)==0 || xml_spec(xml_child_i(x0,0))!=NULL){ + yorder = yang_order(yc); + *x0cp = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval); + } else{ - if (xml_child_nr(x0)==0 || xml_spec(xml_child_i(x0,0))!=NULL){ - yorder = yang_order(yc); - *x0cp = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval); - } - else{ - *x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); - } + *x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); } ok: retval = 0; diff --git a/test/test_cli.sh b/test/test_cli.sh index b4005433..23fee546 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -115,7 +115,13 @@ expectfn "$clixon_cli -1 -f $cfg -l o debug level 1" 0 "^$" expectfn "$clixon_cli -1 -f $cfg -l o debug level 0" 0 "^$" new "cli rpc" -expectfn "$clixon_cli -1 -f $cfg -l o rpc ipv4" 0 "ipv4" "2.3.4.5" +expectfn "$clixon_cli -1 -f $cfg -l o rpc ipv4" 0 'rpc-reply { + route { + address-family ipv4; + next-hop { + next-hop-list 2.3.4.5; + } + source-protocol static;' if [ $BE -eq 0 ]; then exit # BE diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index b706b96f..b9cef0e1 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -209,7 +209,7 @@ new "cli show conf as guest" expectfn "$clixon_cli -1 -U guest -l o -f $cfg -y $fyang show conf" 255 "protocol access-denied" new "cli rpc as admin" -expectfn "$clixon_cli -1 -U andy -l o -f $cfg -y $fyang rpc ipv4" 0 "2.3.4.5" +expectfn "$clixon_cli -1 -U andy -l o -f $cfg -y $fyang rpc ipv4" 0 "next-hop-list 2.3.4.5;" new "cli rpc as limited" expectfn "$clixon_cli -1 -U wilma -l o -f $cfg -y $fyang rpc ipv4" 255 "protocol access-denied default deny" diff --git a/yang/clixon-config@2018-10-21.yang b/yang/clixon-config@2018-10-21.yang index d6b8c2c1..efa777e3 100644 --- a/yang/clixon-config@2018-10-21.yang +++ b/yang/clixon-config@2018-10-21.yang @@ -344,15 +344,6 @@ module clixon-config { If set, insert spaces and line-feeds making the XML/JSON human readable. If not set, make the XML/JSON more compact."; } - leaf CLICON_XML_SORT { - type boolean; - default true; - description - "If set, sort XML lists and leaf-lists alphabetically and uses binary - search. Unless ordered-by user is used. - Only works for Yang specified XML. - If not set, all lists accessed via linear search."; - } leaf CLICON_XML_NS_STRICT { type boolean; default true; From 6c48165340540c865de5d173990f7c1b14599e6d Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 2 Jan 2019 18:20:50 +0100 Subject: [PATCH 07/17] three-valued return values for datastore text_modify --- apps/backend/backend_commit.c | 2 +- datastore/text/clixon_xmldb_text.c | 84 ++++++++++++++++++------------ example/example_backend.c | 12 ++++- lib/src/clixon_xml_db.c | 2 +- 4 files changed, 63 insertions(+), 37 deletions(-) diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 61e9a246..d7c2d6c3 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -285,7 +285,7 @@ candidate_commit(clicon_handle h, /* Optionally write (potentially modified) tree back to candidate */ if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){ - if ((ret = xmldb_put(h, candidate, OP_REPLACE, td->td_target, NULL)) < 0) + if ((ret = xmldb_put(h, candidate, OP_REPLACE, td->td_target, cbret)) < 0) goto done; if (ret == 0) goto fail; diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index 42210170..c4d2d1b4 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -572,7 +572,10 @@ text_get(xmldb_handle xh, * @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[out] cbret Initialized cligen buffer. Contains return XML or "". + * @param[out] cbret Initialized cligen buffer. Contains return XML if retval is 0. + * @retval -1 Error + * @retval 0 Failed (cbret set) + * @retval 1 OK * Assume x0 and x1 are same on entry and that y is the spec * @see text_modify_top */ @@ -598,6 +601,7 @@ text_modify(struct text_handle *th, yang_stmt *yc; /* yang child */ cxobj **x0vec = NULL; int i; + int ret; assert(x1 && xml_type(x1) == CX_ELMNT); assert(y0); @@ -613,7 +617,7 @@ text_modify(struct text_handle *th, if (x0){ if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0) goto done; - goto ok; + goto fail; } case OP_NONE: /* fall thru */ case OP_MERGE: @@ -658,7 +662,7 @@ text_modify(struct text_handle *th, if (x0==NULL){ if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0) goto done; - goto ok; + goto fail; } case OP_REMOVE: /* fall thru */ if (x0){ @@ -675,7 +679,7 @@ text_modify(struct text_handle *th, if (x0){ if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0) goto done; - goto ok; + goto fail; } case OP_REPLACE: /* fall thru */ if (x0){ @@ -738,18 +742,18 @@ text_modify(struct text_handle *th, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); yc = yang_find_datanode(y0, x1cname); - if (text_modify(th, x0vec[i++], (yang_node*)yc, x0, x1c, op, cbret) < 0) + if ((ret = text_modify(th, x0vec[i++], (yang_node*)yc, x0, x1c, op, cbret)) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ - if (cbuf_len(cbret)) - goto ok; + if (ret == 0) + goto fail; } break; case OP_DELETE: if (x0==NULL){ if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0) goto done; - goto ok; + goto fail; } case OP_REMOVE: /* fall thru */ if (x0) @@ -760,21 +764,26 @@ text_modify(struct text_handle *th, } /* CONTAINER switch op */ } /* else Y_CONTAINER */ xml_sort(x0p, NULL); - ok: - retval = 0; + retval = 1; done: if (x0vec) free(x0vec); return retval; + fail: /* cbret set */ + retval = 0; + goto done; } /* text_modify */ /*! Modify a top-level base tree x0 with modification tree x1 - * @param[in] th text handle - * @param[in] x0 Base xml tree (can be NULL in add scenarios) - * @param[in] x1 xml tree which modifies base + * @param[in] th text handle + * @param[in] x0 Base xml tree (can be NULL in add scenarios) + * @param[in] x1 xml tree which modifies base * @param[in] yspec Top-level yang spec (if y is NULL) - * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc - * @param[out] cbret Initialized cligen buffer. Contains return XML or "". + * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc + * @param[out] cbret Initialized cligen buffer. Contains return XML if retval is 0. + * @retval -1 Error + * @retval 0 Failed (cbret set) + * @retval 1 OK * @see text_modify */ static int @@ -792,6 +801,7 @@ text_modify_top(struct text_handle *th, yang_stmt *yc; /* yang child */ yang_stmt *ymod;/* yang module */ char *opstr; + int ret; /* Assure top-levels are 'config' */ assert(x0 && strcmp(xml_name(x0),"config")==0); @@ -820,7 +830,7 @@ text_modify_top(struct text_handle *th, case OP_DELETE: if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0) goto done; - goto ok; + goto fail; break; default: break; @@ -849,21 +859,24 @@ text_modify_top(struct text_handle *th, if (yc == NULL){ if (netconf_unknown_element(cbret, "application", x1cname, "Unassigned yang spec") < 0) goto done; - goto ok; + goto fail; } /* See if there is a corresponding node in the base tree */ if (match_base_child(x0, x1c, &x0c, yc) < 0) goto done; - if (text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret) < 0) + if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret)) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ - if (cbuf_len(cbret)) - goto ok; + if (ret == 0) + goto fail; } - ok: - retval = 0; + // ok: + retval = 1; done: return retval; + fail: /* cbret set */ + retval = 0; + goto done; } /* text_modify_top */ /*! For containers without presence and no children(except attrs), remove @@ -923,14 +936,11 @@ text_put(xmldb_handle xh, yang_spec *yspec; cxobj *x0 = NULL; struct db_element *de = NULL; - int cbretlocal = 0; /* Set if cbret is NULL on entry */ + int ret; if (cbret == NULL){ - if ((cbret = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } - cbretlocal++; + clicon_err(OE_XML, EINVAL, "cbret is NULL"); + goto done; } if ((yspec = th->th_yangspec) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec"); @@ -991,10 +1001,10 @@ text_put(xmldb_handle xh, * Modify base tree x with modification x1. This is where the * new tree is made. */ - if (text_modify_top(th, x0, x1, yspec, op, cbret) < 0) + if ((ret = text_modify_top(th, x0, x1, yspec, op, cbret)) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ - if (cbuf_len(cbret)) + if (ret == 0) goto fail; /* Remove NONE nodes if all subs recursively are also NONE */ @@ -1047,8 +1057,6 @@ text_put(xmldb_handle xh, goto done; retval = 1; done: - if (cbretlocal && cbret) - cbuf_free(cbret); if (f != NULL) fclose(f); if (dbfile) @@ -1408,6 +1416,8 @@ main(int argc, char *yangmod; /* yang file */ yang_spec *yspec = NULL; clicon_handle h; + cbuf *cbret = NULL; + int ret; if ((h = clicon_handle_init()) == NULL) goto done; @@ -1456,13 +1466,21 @@ main(int argc, op = OP_REMOVE; else usage(argv[0]); - if (xmldb_put(h, db, op, NULL, xn, NULL) < 1) + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; + } + if ((ret = xmldb_put(h, db, op, NULL, xn, cbret)) < 0) + goto done; + if (ret == 0) + fprintf(stderr, "%s\n", cbuf_get(cbret)); } else usage(argv[0]); printf("\n"); done: + if (cbret) + cbuf_free(cbret); return 0; } diff --git a/example/example_backend.c b/example/example_backend.c index 7c09ee3d..a322ad1e 100644 --- a/example/example_backend.c +++ b/example/example_backend.c @@ -260,6 +260,7 @@ example_reset(clicon_handle h, int retval = -1; cxobj *xt = NULL; int ret; + cbuf *cbret = NULL; if (xml_parse_string("" "loex:loopback" @@ -268,15 +269,22 @@ example_reset(clicon_handle h, /* Replace parent w first child */ if (xml_rootchild(xt, 0, &xt) < 0) goto done; + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } /* Merge user reset state */ - if ((ret = xmldb_put(h, (char*)db, OP_MERGE, xt, NULL)) < 0) + if ((ret = xmldb_put(h, (char*)db, OP_MERGE, xt, cbret)) < 0) goto done; if (ret == 0){ - clicon_err(OE_XML, 0, "Error when writing to XML database"); + clicon_err(OE_XML, 0, "Error when writing to XML database: %s", + cbuf_get(cbret)); goto done; } retval = 0; done: + if (cbret) + cbuf_free(cbret); if (xt != NULL) xml_free(xt); return retval; diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index 8fb5da70..73959bf7 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -379,7 +379,7 @@ xmldb_get(clicon_handle h, * @param[in] db running or candidate * @param[in] op Top-level operation, can be superceded by other op in tree * @param[in] xt xml-tree. Top-level symbol is dummy - * @param[out] cbret Initialized cligen buffer or NULL. On exit contains XML or "". + * @param[out] cbret Initialized cligen buffer. On exit contains XML if retval == 0 * @retval 1 OK * @retval 0 Failed, cbret contains error xml message * @retval -1 Error From b443471e7b3426c2e3068aa5c89a2f0c1ddd7280 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 2 Jan 2019 18:44:27 +0100 Subject: [PATCH 08/17] Removed CLI generation for yang notifications (and other non-data yang nodes --- CHANGELOG.md | 1 + apps/cli/cli_generate.c | 14 +++++--------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbc70587..82ba6372 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -110,6 +110,7 @@ * ys_keyword){ - case Y_GROUPING: - case Y_RPC: - case Y_AUGMENT: - return 0; - break; case Y_CONTAINER: if (yang2cli_container(h, ys, cbuf, gt, level) < 0) goto done; @@ -773,19 +768,20 @@ yang2cli_stmt(clicon_handle h, if (yang2cli_leaf(h, ys, cbuf, gt, level, 1) < 0) goto done; break; - default: + case Y_SUBMODULE: + case Y_MODULE: for (i=0; iys_len; i++) if ((yc = ys->ys_stmt[i]) != NULL) if (yang2cli_stmt(h, yc, cbuf, gt, level+1) < 0) goto done; break; + default: /* skip */ + break; } } - retval = 0; done: return retval; - } /*! Generate CLI code for Yang specification @@ -814,7 +810,7 @@ yang2cli(clicon_handle h, clicon_err(OE_XML, errno, "cbuf_new"); goto done; } - /* Traverse YANG specification: loop through statements */ + /* Traverse YANG, loop through all modules and generate CLI */ for (i=0; iyp_len; i++) if ((ymod = yspec->yp_stmt[i]) != NULL){ if (yang2cli_stmt(h, ymod, cbuf, gt, 0) < 0) From 11479f7ec3840c1611b1192d84736f205739b004 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 2 Jan 2019 20:59:57 +0100 Subject: [PATCH 09/17] add yang-spec as arg to decode clicon msg --- apps/backend/backend_client.c | 12 +++++++----- apps/cli/cli_common.c | 2 +- apps/netconf/netconf_rpc.c | 7 +++++-- apps/restconf/restconf_stream.c | 2 +- lib/clixon/clixon_proto.h | 2 +- lib/src/clixon_proto.c | 4 +++- lib/src/clixon_xml_map.c | 14 +++++--------- 7 files changed, 23 insertions(+), 20 deletions(-) diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 6a8b5943..a141f09f 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -965,6 +965,7 @@ from_client_msg(clicon_handle h, clicon_debug(1, "%s", __FUNCTION__); pid = ce->ce_pid; + yspec = clicon_dbspec_yang(h); /* Return netconf message. Should be filled in by the dispatch(sub) functions * as wither rpc-error or by positive response. */ @@ -972,21 +973,22 @@ from_client_msg(clicon_handle h, clicon_err(OE_XML, errno, "cbuf_new"); goto done; } - if (clicon_msg_decode(msg, &xt) < 0){ + if (clicon_msg_decode(msg, yspec, &xt) < 0){ if (netconf_malformed_message(cbret, "XML parse error")< 0) goto done; goto reply; } - /* Get yang spec */ - yspec = clicon_dbspec_yang(h); /* XXX maybe move to clicon_msg_decode? */ + if ((x = xpath_first(xt, "/rpc")) == NULL){ if (netconf_malformed_message(cbret, "rpc keyword expected")< 0) goto done; goto reply; } - /* Populate incoming XML tree with yang */ + /* Populate incoming XML tree with yang - + * should really have been dealt with by decode above + * maybe not necessary since it should be */ if (xml_spec_populate_rpc(h, x, yspec) < 0) - goto done; + goto done; if ((ret = xml_yang_validate_rpc(x, cbret)) < 0) goto done; if (ret == 0) diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index ebab38a7..8a52207e 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -959,7 +959,7 @@ cli_notification_cb(int s, event_unreg_fd(s, cli_notification_cb); goto done; } - if (clicon_msg_decode(reply, &xt) < 0) + if (clicon_msg_decode(reply, NULL, &xt) < 0) /* XXX pass yang_spec */ goto done; if ((xe = xpath_first(xt, "//event")) != NULL){ x = NULL; diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index f2bbb605..0770a0b9 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -754,6 +754,8 @@ netconf_notification_cb(int s, cbuf *cb; cxobj *xn = NULL; /* event xml */ cxobj *xt = NULL; /* top xml */ + clicon_handle h = (clicon_handle)arg; + yang_spec *yspec = NULL; clicon_debug(1, "%s", __FUNCTION__); /* get msg (this is the reason this function is called) */ @@ -767,7 +769,8 @@ netconf_notification_cb(int s, event_unreg_fd(s, netconf_notification_cb); goto done; } - if (clicon_msg_decode(reply, &xt) < 0) + yspec = clicon_dbspec_yang(h); + if (clicon_msg_decode(reply, yspec, &xt) < 0) goto done; if ((xn = xpath_first(xt, "notification")) == NULL) goto ok; @@ -839,7 +842,7 @@ netconf_create_subscription(clicon_handle h, goto ok; if (event_reg_fd(s, netconf_notification_cb, - NULL, + h, "notification socket") < 0) goto done; ok: diff --git a/apps/restconf/restconf_stream.c b/apps/restconf/restconf_stream.c index 667db0ad..3d34c798 100644 --- a/apps/restconf/restconf_stream.c +++ b/apps/restconf/restconf_stream.c @@ -180,7 +180,7 @@ restconf_stream_cb(int s, clicon_exit_set(); goto done; } - if (clicon_msg_decode(reply, &xtop) < 0) + if (clicon_msg_decode(reply, NULL, &xtop) < 0) /* XXX pass yang_spec */ goto done; /* create event */ if ((cb = cbuf_new()) == NULL){ diff --git a/lib/clixon/clixon_proto.h b/lib/clixon/clixon_proto.h index 3d9e6603..e5457685 100644 --- a/lib/clixon/clixon_proto.h +++ b/lib/clixon/clixon_proto.h @@ -66,7 +66,7 @@ struct clicon_msg *clicon_msg_encode(char *format, ...) __attribute__ ((format ( #else struct clicon_msg *clicon_msg_encode(char *format, ...); #endif -int clicon_msg_decode(struct clicon_msg *msg, cxobj **xml); +int clicon_msg_decode(struct clicon_msg *msg, yang_spec *yspec, cxobj **xml); int clicon_connect_unix(char *sockpath); diff --git a/lib/src/clixon_proto.c b/lib/src/clixon_proto.c index 30da4e59..76c7816e 100644 --- a/lib/src/clixon_proto.c +++ b/lib/src/clixon_proto.c @@ -160,10 +160,12 @@ clicon_msg_encode(char *format, ...) /*! Decode a clicon netconf message * @param[in] msg CLICON msg + * @param[in] yspec Yang specification, (can be NULL) * @param[out] xml XML parse tree */ int clicon_msg_decode(struct clicon_msg *msg, + yang_spec *yspec, cxobj **xml) { int retval = -1; @@ -172,7 +174,7 @@ clicon_msg_decode(struct clicon_msg *msg, /* body */ xmlstr = msg->op_body; clicon_debug(1, "%s %s", __FUNCTION__, xmlstr); - if (xml_parse_string(xmlstr, NULL, xml) < 0) + if (xml_parse_string(xmlstr, yspec, xml) < 0) goto done; retval = 0; done: diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index d3585ee4..660c4849 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -1623,7 +1623,7 @@ xml_spec_populate_rpc(clicon_handle h, yang_stmt *ymod=NULL; /* yang module */ yang_stmt *yi = NULL; /* input */ cxobj *x; - int i; + // int i; if ((strcmp(xml_name(xrpc), "rpc"))!=0){ clicon_err(OE_UNIX, EINVAL, "RPC expected"); @@ -1635,15 +1635,12 @@ xml_spec_populate_rpc(clicon_handle h, goto done; if (ymod != NULL) y = yang_find((yang_node*)ymod, Y_RPC, xml_name(x)); - /* Loose semantics: loop through all modules to find the node + /* Non-strict semantics: loop through all modules to find the node */ if (y == NULL && !clicon_option_bool(h, "CLICON_XML_NS_STRICT")){ - for (i=0; iyp_len; i++){ - ymod = yspec->yp_stmt[i]; - if ((y = yang_find((yang_node*)ymod, Y_RPC, xml_name(x))) != NULL) - break; - } + if (xml_yang_find_non_strict(x, yspec, &y) < 0) /* find rpc */ + goto done; } if (y){ xml_spec_set(x, y); @@ -1696,9 +1693,8 @@ xml_spec_populate(cxobj *x, if (ys_module_by_xml(yspec, x, &ymod) < 0) goto done; if (ymod != NULL) - y = yang_find_datanode((yang_node*)ymod, name); + y = yang_find_schemanode((yang_node*)ymod, name); /* Non-strict semantics: loop through all modules to find the node - * XXX clicon_option_bool(h, "CLICON_XML_NS_STRICT") */ if (y == NULL && !_CLICON_XML_NS_STRICT){ if (xml_yang_find_non_strict(x, yspec, &y) < 0) From 7a8f242a09f3403ae1473a70132bd27b42da70a4 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 2 Jan 2019 22:50:03 +0100 Subject: [PATCH 10/17] Added new clixon-lib yang module for internal netconf protocol. Currently only extends the standard with a debug RPC. --- CHANGELOG.md | 1 + apps/backend/backend_main.c | 3 + apps/cli/cli_main.c | 3 + apps/netconf/netconf_main.c | 4 +- apps/restconf/restconf_main.c | 4 +- apps/restconf/restconf_methods.c | 12 +- example/example.yang | 9 - lib/src/clixon_proto_client.c | 2 +- test/test_install.sh | 6 +- test/test_restconf.sh | 4 +- yang/Makefile.in | 2 + yang/clixon-config@2018-10-21.yang | 10 +- yang/clixon-lib@2019-01-02.yang | 55 ++++++ yang/ietf-restconf@2017-01-26.yang | 279 +++++++++++++++++++++++++++++ 14 files changed, 362 insertions(+), 32 deletions(-) create mode 100644 yang/clixon-lib@2019-01-02.yang create mode 100644 yang/ietf-restconf@2017-01-26.yang diff --git a/CHANGELOG.md b/CHANGELOG.md index 82ba6372..588c5e10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,6 +94,7 @@ * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h ### Minor changes +* Added new clixon-lib yang module for internal netconf protocol. Currently only extends the standard with a debug RPC. * Added three-valued return values for several validate functions where -1 is fatal error, 0 is validation failed and 1 is validation OK. * This includes: `xmldb_put`, `xml_yang_validate_all`, `xml_yang_validate_add`, `xml_yang_validate_rpc`, `api_path2xml`, `api_path2xpath` * Added new xml functions for specific types: `xml_child_nr_notype`, `xml_child_nr_notype`, `xml_child_i_type`, `xml_find_type`. diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 8e05c175..6fbb8750 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -805,6 +805,9 @@ main(int argc, if ((str = clicon_yang_main_dir(h)) != NULL) if (yang_spec_load_dir(h, str, yspec) < 0) goto done; + /* Load clixon lib yang module */ + if (yang_spec_parse_module(h, "clixon-lib", NULL, yspec) < 0) + goto done; /* Load yang module library, RFC7895 */ if (yang_modules_init(h) < 0) goto done; diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index e6d7925a..ce994fee 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -446,6 +446,9 @@ main(int argc, char **argv) if (yang_spec_load_dir(h, str, yspec) < 0) goto done; } + /* Load clixon lib yang module */ + if (yang_spec_parse_module(h, "clixon-lib", NULL, yspec) < 0) + goto done; /* Load yang module library, RFC7895 */ if (yang_modules_init(h) < 0) diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index e60a5d08..eadbcc1b 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -468,7 +468,9 @@ main(int argc, if (yang_spec_load_dir(h, str, yspec) < 0) goto done; } - + /* Load clixon lib yang module */ + if (yang_spec_parse_module(h, "clixon-lib", NULL, yspec) < 0) + goto done; /* Load yang module library, RFC7895 */ if (yang_modules_init(h) < 0) goto done; diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index e6234884..0819c7cb 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -645,7 +645,9 @@ main(int argc, if (yang_spec_load_dir(h, str, yspec) < 0) goto done; } - + /* Load clixon lib yang module */ + if (yang_spec_parse_module(h, "clixon-lib", NULL, yspec) < 0) + goto done; /* Load yang module library, RFC7895 */ if (yang_modules_init(h) < 0) goto done; diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 8f3cb4a5..58980589 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -1132,27 +1132,23 @@ api_operations_get(clicon_handle h, if (use_xml) cprintf(cbx, ""); else - cprintf(cbx, "{\"operations\": "); + cprintf(cbx, "{\"operations\": {"); ymod = NULL; + i = 0; while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) { namespace = yang_find_mynamespace(ymod); - yc = NULL; i=0; + yc = NULL; while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) { if (yc->ys_keyword != Y_RPC) continue; if (use_xml) cprintf(cbx, "<%s xmlns=\"%s\"/>", yc->ys_argument, namespace); else{ - if (i==0) - cprintf(cbx, "{"); - if (i) + if (i++) cprintf(cbx, ","); cprintf(cbx, "\"%s:%s\": null", ymod->ys_argument, yc->ys_argument); } - i++; } - if (!use_xml && i) - cprintf(cbx, "}"); } if (use_xml) cprintf(cbx, ""); diff --git a/example/example.yang b/example/example.yang index 749b10ff..f5e82063 100644 --- a/example/example.yang +++ b/example/example.yang @@ -128,13 +128,4 @@ module example { } } } - rpc debug { - description "Set debug level of backend. XXX should be in clixon-config"; - input { - leaf level { - type uint32; - } - } - } - } diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 0dbb76d1..b454b608 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -827,7 +827,7 @@ clicon_rpc_debug(clicon_handle h, username = clicon_username_get(h); /* XXX: hardcoded example yang, should be clixon-config!!! */ - if ((msg = clicon_msg_encode("%d", username?username:"", level)) == NULL) + if ((msg = clicon_msg_encode("%d", username?username:"", level)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; diff --git a/test/test_install.sh b/test/test_install.sh index 032bba4a..09867c42 100755 --- a/test/test_install.sh +++ b/test/test_install.sh @@ -12,19 +12,23 @@ if [ $? -ne 0 ]; then err fi -new "Check installed files" +new "Check installed files /usr" if [ ! -d $dir/usr ]; then err $dir/usr fi +new "Check installed files /www-data" if [ ! -d $dir/www-data ]; then err $dir/www-data fi +new "Check installed files clixon-config" if [ ! -f $dir/usr/local/share/clixon/clixon-config* ]; then err $dir/usr/local/share/clixon/clixon-config* fi +new "Check installed files libclixon.so" if [ ! -h $dir/usr/local/lib/libclixon.so ]; then err $dir/usr/local/lib/libclixon.so fi +new "Check installed files libclixon_backend.so" if [ ! -h $dir/usr/local/lib/libclixon_backend.so ]; then err $dir/usr/local/lib/libclixon_backend.so fi diff --git a/test/test_restconf.sh b/test/test_restconf.sh index e7451d0a..2a1fd961 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -118,12 +118,12 @@ expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/r # Should be alphabetically ordered new2 "restconf get restconf/operations. RFC8040 3.3.2 (json)" -expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"example:empty": null,"example:client-rpc": null}{"ietf-routing:fib-route": null,"ietf-routing:route-count": null}} +expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"example:empty": null,"example:client-rpc": null,"ietf-routing:fib-route": null,"ietf-routing:route-count": null,"clixon-lib:debug": null} ' new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) -expect='' +expect='' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" diff --git a/yang/Makefile.in b/yang/Makefile.in index fb32b74d..a1d0be57 100644 --- a/yang/Makefile.in +++ b/yang/Makefile.in @@ -41,10 +41,12 @@ datarootdir = @datarootdir@ CLIXON_DATADIR = @CLIXON_DATADIR@ YANGSPECS = clixon-config@2018-10-21.yang +YANGSPECS += clixon-lib@2019-01-02.yang YANGSPECS += ietf-netconf@2011-06-01.yang YANGSPECS += ietf-netconf-acm@2018-02-14.yang YANGSPECS += ietf-inet-types@2013-07-15.yang YANGSPECS += ietf-yang-types@2013-07-15.yang +YANGSPECS += ietf-restconf@2017-01-26.yang YANGSPECS += ietf-restconf-monitoring@2017-01-26.yang YANGSPECS += ietf-netconf-notification@2008-07-01.yang YANGSPECS += ietf-yang-library@2016-06-21.yang diff --git a/yang/clixon-config@2018-10-21.yang b/yang/clixon-config@2018-10-21.yang index efa777e3..a9ad7414 100644 --- a/yang/clixon-config@2018-10-21.yang +++ b/yang/clixon-config@2018-10-21.yang @@ -1,6 +1,6 @@ module clixon-config { yang-version 1.1; - namespace "http://clicon.org"; + namespace "http://clicon.org/config"; prefix cc; organization @@ -455,12 +455,4 @@ module clixon-config { } } - rpc debug { - description "Set debug level of backend."; - input { - leaf level { - type uint32; - } - } - } } diff --git a/yang/clixon-lib@2019-01-02.yang b/yang/clixon-lib@2019-01-02.yang new file mode 100644 index 00000000..1e26187b --- /dev/null +++ b/yang/clixon-lib@2019-01-02.yang @@ -0,0 +1,55 @@ +module clixon-lib { + yang-version 1.1; + namespace "http://clicon.org/lib"; + prefix cl; + + organization + "Clicon / Clixon"; + + contact + "Olof Hagsand "; + + description + "Clixon Netconf extensions for communication between clients and backend. + + ***** BEGIN LICENSE BLOCK ***** + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON + + Licensed under the Apache License, Version 2.0 (the \"License\"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an \"AS IS\" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the \"GPL\"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK *****"; + + revision 2019-01-02 { + description + "Released in Clixon 3.9"; + } + rpc debug { + description "Set debug level of backend."; + input { + leaf level { + type uint32; + } + } + } +} diff --git a/yang/ietf-restconf@2017-01-26.yang b/yang/ietf-restconf@2017-01-26.yang new file mode 100644 index 00000000..249006af --- /dev/null +++ b/yang/ietf-restconf@2017-01-26.yang @@ -0,0 +1,279 @@ +module ietf-restconf { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-restconf"; + prefix "rc"; + + organization + "IETF NETCONF (Network Configuration) Working Group"; + + contact + "WG Web: + WG List: + + Author: Andy Bierman + + + Author: Martin Bjorklund + + + Author: Kent Watsen + "; + + description + "This module contains conceptual YANG specifications + for basic RESTCONF media type definitions used in + RESTCONF protocol messages. + + Note that the YANG definitions within this module do not + represent configuration data of any kind. + The 'restconf-media-type' YANG extension statement + provides a normative syntax for XML and JSON + message-encoding purposes. + + Copyright (c) 2017 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8040; see + the RFC itself for full legal notices."; + + revision 2017-01-26 { + description + "Initial revision."; + reference + "RFC 8040: RESTCONF Protocol."; + } + + extension yang-data { + argument name { + yin-element true; + } + description + "This extension is used to specify a YANG data template that + represents conceptual data defined in YANG. It is + intended to describe hierarchical data independent of + protocol context or specific message-encoding format. + Data definition statements within a yang-data extension + specify the generic syntax for the specific YANG data + template, whose name is the argument of the 'yang-data' + extension statement. + + Note that this extension does not define a media type. + A specification using this extension MUST specify the + message-encoding rules, including the content media type. + + The mandatory 'name' parameter value identifies the YANG + data template that is being defined. It contains the + template name. + + This extension is ignored unless it appears as a top-level + statement. It MUST contain data definition statements + that result in exactly one container data node definition. + An instance of a YANG data template can thus be translated + into an XML instance document, whose top-level element + corresponds to the top-level container. + + The module name and namespace values for the YANG module using + the extension statement are assigned to instance document data + conforming to the data definition statements within + this extension. + + The substatements of this extension MUST follow the + 'data-def-stmt' rule in the YANG ABNF. + + The XPath document root is the extension statement itself, + such that the child nodes of the document root are + represented by the data-def-stmt substatements within + this extension. This conceptual document is the context + for the following YANG statements: + + - must-stmt + - when-stmt + - path-stmt + - min-elements-stmt + - max-elements-stmt + - mandatory-stmt + - unique-stmt + - ordered-by + - instance-identifier data type + + The following data-def-stmt substatements are constrained + when used within a 'yang-data' extension statement. + + - The list-stmt is not required to have a key-stmt defined. + - The if-feature-stmt is ignored if present. + - The config-stmt is ignored if present. + - The available identity values for any 'identityref' + leaf or leaf-list nodes are limited to the module + containing this extension statement and the modules + imported into that module. + "; + } + + rc:yang-data yang-errors { + uses errors; + } + + rc:yang-data yang-api { + uses restconf; + } + + grouping errors { + description + "A grouping that contains a YANG container + representing the syntax and semantics of a + YANG Patch error report within a response message."; + + container errors { + description + "Represents an error report returned by the server if + a request results in an error."; + + list error { + description + "An entry containing information about one + specific error that occurred while processing + a RESTCONF request."; + reference + "RFC 6241, Section 4.3."; + + leaf error-type { + type enumeration { + enum transport { + description + "The transport layer."; + } + enum rpc { + description + "The rpc or notification layer."; + } + enum protocol { + description + "The protocol operation layer."; + } + enum application { + description + "The server application layer."; + } + } + mandatory true; + description + "The protocol layer where the error occurred."; + } + + leaf error-tag { + type string; + mandatory true; + description + "The enumerated error-tag."; + } + + leaf error-app-tag { + type string; + description + "The application-specific error-tag."; + } + + leaf error-path { + type instance-identifier; + description + "The YANG instance identifier associated + with the error node."; + } + + leaf error-message { + type string; + description + "A message describing the error."; + } + + anydata error-info { + description + "This anydata value MUST represent a container with + zero or more data nodes representing additional + error information."; + } + } + } + } + + grouping restconf { + description + "Conceptual grouping representing the RESTCONF + root resource."; + + container restconf { + description + "Conceptual container representing the RESTCONF + root resource."; + container data { + description + "Container representing the datastore resource. + Represents the conceptual root of all state data + and configuration data supported by the server. + The child nodes of this container can be any data + resources that are defined as top-level data nodes + from the YANG modules advertised by the server in + the 'ietf-yang-library' module."; + } + + container operations { + description + "Container for all operation resources. + + Each resource is represented as an empty leaf with the + name of the RPC operation from the YANG 'rpc' statement. + + For example, the 'system-restart' RPC operation defined + in the 'ietf-system' module would be represented as + an empty leaf in the 'ietf-system' namespace. This is + a conceptual leaf and will not actually be found in + the module: + + module ietf-system { + leaf system-reset { + type empty; + } + } + + To invoke the 'system-restart' RPC operation: + + POST /restconf/operations/ietf-system:system-restart + + To discover the RPC operations supported by the server: + + GET /restconf/operations + + In XML, the YANG module namespace identifies the module: + + + + In JSON, the YANG module name identifies the module: + + { 'ietf-system:system-restart' : [null] } + "; + } + + leaf yang-library-version { + type string { + pattern '\d{4}-\d{2}-\d{2}'; + } + config false; + mandatory true; + description + "Identifies the revision date of the 'ietf-yang-library' + module that is implemented by this RESTCONF server. + Indicates the year, month, and day in YYYY-MM-DD + numeric format."; + } + } + } + +} From 058a14579f4969258de84c4f82d76fd3c1b6e1d7 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Fri, 4 Jan 2019 11:37:26 +0100 Subject: [PATCH 11/17] Yang choice functionality improved and stricter validation for CLI generation, mandatory flags, etc. --- CHANGELOG.md | 2 + apps/cli/cli_generate.c | 1 + apps/netconf/netconf_main.c | 17 ++- apps/netconf/netconf_rpc.c | 15 +- datastore/text/clixon_xmldb_text.c | 35 ++++- lib/clixon/clixon_xml_sort.h | 2 +- lib/clixon/clixon_yang.h | 1 + lib/src/clixon_json_parse.y | 2 +- lib/src/clixon_xml_map.c | 106 ++++++++++----- lib/src/clixon_xml_sort.c | 56 +++++--- lib/src/clixon_xpath_parse.y | 2 +- lib/src/clixon_yang.c | 24 +++- lib/src/clixon_yang_parse.y | 2 +- test/test_choice.sh | 212 +++++++++++++++++++++++++++++ test/test_leafref.sh | 2 +- test/test_rpc.sh | 19 ++- 16 files changed, 412 insertions(+), 86 deletions(-) create mode 100755 test/test_choice.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 588c5e10..58ca127d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,7 @@ * CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded. ### API changes on existing features (you may need to change your code) +* Stricter YANG choice validation leads to enforcement of structures like: `choice c{ mandatory true; leaf x` statements. `x` was not previously enforced. * CLICON_XML_SORT option (in clixon-config.yang) has been removed and set to true permanently since setting it to false is obsolete. * Strict namespace setting can be a problem when upgrading existing database files, such as startup-db or persistent running-db, or any other saved XML file. * For backward compatibility, load of startup and running set CLICON_XML_NS_STRICT to false temporarily. @@ -94,6 +95,7 @@ * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h ### Minor changes +* Yang choice functionality improved and stricter validation for CLI generation, mandatory flags, etc. * Added new clixon-lib yang module for internal netconf protocol. Currently only extends the standard with a debug RPC. * Added three-valued return values for several validate functions where -1 is fatal error, 0 is validation failed and 1 is validation OK. * This includes: `xmldb_put`, `xml_yang_validate_all`, `xml_yang_validate_add`, `xml_yang_validate_rpc`, `api_path2xml`, `api_path2xpath` diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 8c87a0d7..b91a5242 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -768,6 +768,7 @@ yang2cli_stmt(clicon_handle h, if (yang2cli_leaf(h, ys, cbuf, gt, level, 1) < 0) goto done; break; + case Y_CASE: case Y_SUBMODULE: case Y_MODULE: for (i=0; iys_len; i++) diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index eadbcc1b..7e1163dc 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -80,7 +80,7 @@ * @param[in] cb Packet buffer */ static int -process_incoming_packet(clicon_handle h, +netconf_input_packet(clicon_handle h, cbuf *cb) { int retval = -1; @@ -94,6 +94,8 @@ process_incoming_packet(clicon_handle h, cxobj *xc; yang_spec *yspec; int ret; + cxobj *xa; + cxobj *xa2; clicon_debug(1, "RECV"); clicon_debug(2, "%s: RCV: \"%s\"", __FUNCTION__, cbuf_get(cb)); @@ -110,7 +112,7 @@ process_incoming_packet(clicon_handle h, /* Parse incoming XML message */ if (xml_parse_string(str, yspec, &xreq) < 0){ free(str0); - if (netconf_operation_failed(cbret, "rpc", "internal error")< 0) + if (netconf_operation_failed(cbret, "rpc", clicon_err_reason)< 0) goto done; netconf_output_encap(1, cbret, "rpc-error"); goto done; @@ -143,8 +145,13 @@ process_incoming_packet(clicon_handle h, goto done; } else{ /* there is a return message in xret */ - cxobj *xa, *xa2; - assert(xret); + + if (xret == NULL){ + if (netconf_operation_failed(cbret, "rpc", "Internal error: no xml return")< 0) + goto done; + netconf_output_encap(1, cbret, "rpc-error"); + goto done; + } if ((xc = xml_child_i(xret,0))!=NULL){ xa=NULL; /* Copy message-id attribute from incoming to reply. @@ -228,7 +235,7 @@ netconf_input_cb(int s, /* OK, we have an xml string from a client */ /* Remove trailer */ *(((char*)cbuf_get(cb)) + cbuf_len(cb) - strlen("]]>]]>")) = '\0'; - if (process_incoming_packet(h, cb) < 0) + if (netconf_input_packet(h, cb) < 0) ; //goto done; // ignore errors if (cc_closed) break; diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 0770a0b9..21a02193 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -314,7 +314,6 @@ netconf_edit_config(clicon_handle h, enum operation_type operation = OP_MERGE; enum test_option testopt = TEST_THEN_SET;/* only supports this */ enum error_option erropt = STOP_ON_ERROR; /* only supports this */ - cxobj *xc; /* config */ cxobj *x; cxobj *xfilter; char *ftype = NULL; @@ -366,18 +365,8 @@ netconf_edit_config(clicon_handle h, ""); goto ok; } - - /* operation is OP_REPLACE, OP_MERGE, or OP_NONE pass all to backend */ - if ((xc = xpath_first(xn, "config")) != NULL){ -#if 0 - /* application-specific code registers 'config' */ - if ((ret = netconf_plugin_callbacks(h, xc, xret)) < 0){ - goto ok; - } -#endif - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - } + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + goto done; ok: retval = 0; done: diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index c4d2d1b4..689e9a4b 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -715,8 +715,9 @@ text_modify(struct text_handle *th, if (op==OP_NONE) xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */ } - /* First pass: mark existing children in base */ - /* Loop through children of the modification tree */ + /* First pass: Loop through children of the x1 modification tree + * collect matching nodes from x0 in x0vec (no changes to x0 children) + */ if ((x0vec = calloc(xml_child_nr(x1), sizeof(x1))) == NULL){ clicon_err(OE_UNIX, errno, "calloc"); goto done; @@ -732,17 +733,29 @@ text_modify(struct text_handle *th, } /* See if there is a corresponding node in the base tree */ x0c = NULL; - if (match_base_child(x0, x1c, &x0c, yc) < 0) + if (match_base_child(x0, x1c, yc, &x0c) < 0) goto done; - x0vec[i++] = x0c; +#if 1 + if (x0c && (yc != xml_spec(x0c))){ + /* There is a match but is should be replaced (choice)*/ + if (xml_purge(x0c) < 0) + goto done; + x0c = NULL; + } +#endif + x0vec[i++] = x0c; /* != NULL if x0c is matching x1c */ } - /* Second pass: modify tree */ + /* Second pass: Loop through children of the x1 modification tree again + * Now potentially modify x0:s children + * Here x0vec contains one-to-one matching nodes of x1:s children. + */ x1c = NULL; i = 0; while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); + x0c = x0vec[i++]; yc = yang_find_datanode(y0, x1cname); - if ((ret = text_modify(th, x0vec[i++], (yang_node*)yc, x0, x1c, op, cbret)) < 0) + if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret)) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ if (ret == 0) @@ -862,8 +875,16 @@ text_modify_top(struct text_handle *th, goto fail; } /* See if there is a corresponding node in the base tree */ - if (match_base_child(x0, x1c, &x0c, yc) < 0) + if (match_base_child(x0, x1c, yc, &x0c) < 0) goto done; +#if 1 + if (x0c && (yc != xml_spec(x0c))){ + /* There is a match but is should be replaced (choice)*/ + if (xml_purge(x0c) < 0) + goto done; + x0c = NULL; + } +#endif if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret)) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ diff --git a/lib/clixon/clixon_xml_sort.h b/lib/clixon/clixon_xml_sort.h index 83f94f44..8b1e6f82 100644 --- a/lib/clixon/clixon_xml_sort.h +++ b/lib/clixon/clixon_xml_sort.h @@ -48,6 +48,6 @@ int xml_insert_pos(cxobj *x0, char *name, int yangi, enum rfc_6020 keyword, int upper); cxobj *xml_match(cxobj *x0, char *name, enum rfc_6020 keyword, int keynr, char **keyvec, char **keyval); int xml_sort_verify(cxobj *x, void *arg); -int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, yang_stmt *yc); +int match_base_child(cxobj *x0, cxobj *x1c, yang_stmt *yc, cxobj **x0cp); #endif /* _CLIXON_XML_SORT_H */ diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index fb727dfd..b0635385 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -264,6 +264,7 @@ yang_stmt *yang_find_datanode(yang_node *yn, char *argument); yang_stmt *yang_find_schemanode(yang_node *yn, char *argument); char *yang_find_myprefix(yang_stmt *ys); char *yang_find_mynamespace(yang_stmt *ys); +yang_node *yang_choice(yang_stmt *y); int yang_order(yang_stmt *y); int yang_print(FILE *f, yang_node *yn); int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal); diff --git a/lib/src/clixon_json_parse.y b/lib/src/clixon_json_parse.y index a6533a06..07da89a4 100644 --- a/lib/src/clixon_json_parse.y +++ b/lib/src/clixon_json_parse.y @@ -103,7 +103,7 @@ object. #define _YYERROR(msg) {clicon_err(OE_XML, 0, "YYERROR %s '%s' %d", (msg), clixon_json_parsetext, _JY->jy_linenum); YYERROR;} -/* add _yy to error paramaters */ +/* add _yy to error parameters */ #define YY_(msgid) msgid #include "clixon_config.h" diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 660c4849..4b51e30b 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -189,13 +189,14 @@ xml2cli(FILE *f, if (ys->ys_keyword == Y_LEAF || ys->ys_keyword == Y_LEAF_LIST){ if (prepend0) fprintf(f, "%s", prepend0); - body = xml_body(x); if (gt == GT_ALL || gt == GT_VARS) fprintf(f, "%s ", xml_name(x)); - if (index(body, ' ')) - fprintf(f, "\"%s\"", body); - else - fprintf(f, "%s", body); + if ((body = xml_body(x)) != NULL){ + if (index(body, ' ')) + fprintf(f, "\"%s\"", body); + else + fprintf(f, "%s", body); + } fprintf(f, "\n"); goto ok; } @@ -508,32 +509,56 @@ xml_yang_validate_add(cxobj *xt, int retval = -1; cg_var *cv = NULL; char *reason = NULL; - yang_stmt *yc; + yang_stmt *yt; /* yang spec of xt going in */ + yang_stmt *yc; /* yang spec of yt child */ + yang_stmt *yx; /* yang spec of xt children */ + yang_node *yp; /* yang spec of parent of yang spec of xt children */ int i; - yang_stmt *ys; char *body; int ret; cxobj *x; /* if not given by argument (overide) use default link and !Node has a config sub-statement and it is false */ - if ((ys = xml_spec(xt)) != NULL && yang_config(ys) != 0){ - switch (ys->ys_keyword){ + if ((yt = xml_spec(xt)) != NULL && yang_config(yt) != 0){ + switch (yt->ys_keyword){ case Y_RPC: case Y_INPUT: case Y_LIST: /* fall thru */ case Y_CONTAINER: - for (i=0; iys_len; i++){ - yc = ys->ys_stmt[i]; - if (yc->ys_keyword != Y_LEAF) - continue; - if (yang_config(yc)==0) - continue; - if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){ - if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory variable") < 0) - goto done; - goto fail; + for (i=0; iys_len; i++){ + yc = yt->ys_stmt[i]; + switch (yc->ys_keyword){ + case Y_LEAF: + if (yang_config(yc)==0) + break; + if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){ + if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory variable") < 0) + goto done; + goto fail; + } + break; + case Y_CHOICE: + if (yang_mandatory(yc)){ + /* If there is one child with this choice as parent */ + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if ((yx = xml_spec(x)) != NULL && + (yp = yang_choice(yx)) != NULL && + yp == (yang_node*)yc){ + break; /* leave loop with x set */ + } + } + if (x == NULL){ /* No hit */ + if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory choice") < 0) + goto done; + goto fail; + } + } + break; + default: + break; } } break; @@ -541,7 +566,7 @@ xml_yang_validate_add(cxobj *xt, /* fall thru */ case Y_LEAF_LIST: /* validate value against ranges, etc */ - if ((cv = cv_dup(ys->ys_cv)) == NULL){ + if ((cv = cv_dup(yt->ys_cv)) == NULL){ clicon_err(OE_UNIX, errno, "cv_dup"); goto done; } @@ -550,12 +575,12 @@ xml_yang_validate_add(cxobj *xt, */ if ((body = xml_body(xt)) != NULL){ if (cv_parse1(body, cv, &reason) != 1){ - if (netconf_bad_element(cbret, "application", ys->ys_argument, reason) < 0) + if (netconf_bad_element(cbret, "application", yt->ys_argument, reason) < 0) goto done; goto fail; } - if ((ys_cv_validate(cv, ys, &reason)) != 1){ - if (netconf_bad_element(cbret, "application", ys->ys_argument, reason) < 0) + if ((ys_cv_validate(cv, yt, &reason)) != 1){ + if (netconf_bad_element(cbret, "application", yt->ys_argument, reason) < 0) goto done; goto fail; } @@ -948,12 +973,12 @@ cvec2xml_1(cvec *cvv, } /*! Recursive help function to compute differences between two xml trees - * @param[in] x1 First XML tree - * @param[in] x2 Second XML tree + * @param[in] x1 First XML tree + * @param[in] x2 Second XML tree * @param[out] x1vec Pointervector to XML nodes existing in only first tree * @param[out] x1veclen Length of first vector - * @param[out] x2vec Pointervector to XML nodes existing in only second tree - * @param[out] x2veclen Length of x2vec vector + * @param[out] x2vec Pointervector to XML nodes existing in only second tree + * @param[out] x2veclen Length of x2vec vector * @param[out] changed_x1 Pointervector to XML nodes changed orig value * @param[out] changed_x2 Pointervector to XML nodes changed wanted value * @param[out] changedlen Length of changed vector @@ -988,13 +1013,21 @@ xml_diff1(yang_stmt *ys, clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x1c)); goto done; } - if (match_base_child(x2, x1c, &x2c, yc) < 0) + if (match_base_child(x2, x1c, yc, &x2c) < 0) goto done; if (x2c == NULL){ if (cxvec_append(x1c, x1vec, x1veclen) < 0) goto done; } - else{ + else if (yang_choice(yc)){ + /* if x1c and x2c are choice/case, then they are changed */ + if (cxvec_append(x1c, changed_x1, changedlen) < 0) + goto done; + (*changedlen)--; /* append two vectors */ + if (cxvec_append(x2c, changed_x2, changedlen) < 0) + goto done; + } + else{ /* if x1c and x2c are leafs w bodies, then they are changed */ if (yc->ys_keyword == Y_LEAF){ if ((b1 = xml_body(x1c)) == NULL) /* empty type */ break; @@ -1024,7 +1057,7 @@ xml_diff1(yang_stmt *ys, clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x2c)); goto done; } - if (match_base_child(x1, x2c, &x1c, yc) < 0) + if (match_base_child(x1, x2c, yc, &x1c) < 0) goto done; if (x1c == NULL) if (cxvec_append(x2c, x2vec, x2veclen) < 0) @@ -1118,7 +1151,8 @@ yang2api_path_fmt_1(yang_stmt *ys, yp->yn_keyword != Y_SUBMODULE){ if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) /* recursive call */ goto done; - cprintf(cb, "/"); + if (yp->yn_keyword != Y_CHOICE && yp->yn_keyword != Y_CASE) + cprintf(cb, "/"); } else /* top symbol - mark with name prefix */ cprintf(cb, "/%s:", yp->yn_argument); @@ -1128,14 +1162,12 @@ yang2api_path_fmt_1(yang_stmt *ys, cprintf(cb, "%s", ys->ys_argument); } else{ -#if 1 if (ys->ys_keyword == Y_LEAF && yp && yp->yn_keyword == Y_LIST){ if (yang_key_match(yp, ys->ys_argument) == 0) cprintf(cb, "%s", ys->ys_argument); /* Not if leaf and key */ } else -#endif if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE) cprintf(cb, "%s", ys->ys_argument); } @@ -2202,7 +2234,7 @@ xml_merge1(cxobj *x0, } /* See if there is a corresponding node in the base tree */ x0c = NULL; - if (yc && match_base_child(x0, x1c, &x0c, yc) < 0) + if (yc && match_base_child(x0, x1c, yc, &x0c) < 0) goto done; if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0) goto done; @@ -2284,8 +2316,12 @@ xml_merge(cxobj *x0, break; } /* See if there is a corresponding node in the base tree */ - if (match_base_child(x0, x1c, &x0c, yc) < 0) + if (match_base_child(x0, x1c, yc, &x0c) < 0) goto done; + /* There is a case where x0c and x1c are choice nodes, if so, + * it is treated as a match, and x0c will remain + * If it is overwritten, then x0c should be removed here. + */ if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0) goto done; if (*reason != NULL) diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index 7f08b35e..fb5e412b 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -75,6 +75,8 @@ * @param[in] xp XML parent, can be NULL. * @param[in] yspec Yang specification (top level) * @param[out] yresult Pointer to yang stmt of result, or NULL, if not found + * @retval 0 OK + * @retval -1 Error * @note special rule for rpc, ie ,look for top "foo" node. * @note works for import prefix, but not work for generic XML parsing where * xmlns and xmlns:ns are used. @@ -555,22 +557,18 @@ xml_sort_verify(cxobj *x0, } /*! Given child tree x1c, find matching child in base tree x0 and return as x0cp - * param[in] x0 Base tree node - * param[in] x1c Modification tree child - * param[in] yc Yang spec of tree child - * param[out] x0cp Matching base tree child (if any) - * @note XXX: room for optimization? on 1K calls we have 1M body calls and - 500K xml_child_each/cvec_each calls. - The outer loop is large for large lists - The inner loop is small - Major time in xml_find_body() - Can one do a binary search in the x0 list? -*/ + * @param[in] x0 Base tree node + * @param[in] x1c Modification tree child + * @param[in] yc Yang spec of tree child + * @param[out] x0cp Matching base tree child (if any) + * @retval 0 OK + * @retval -1 Error + */ int -match_base_child(cxobj *x0, - cxobj *x1c, - cxobj **x0cp, - yang_stmt *yc) +match_base_child(cxobj *x0, + cxobj *x1c, + yang_stmt *yc, + cxobj **x0cp) { int retval = -1; cvec *cvk = NULL; /* vector of index keys */ @@ -582,8 +580,29 @@ match_base_child(cxobj *x0, char **keyvec = NULL; int i; int yorder; + cxobj *x0c = NULL; + yang_stmt *y0c; + yang_node *y0p; + yang_node *yp; /* yang parent */ - *x0cp = NULL; /* return value */ + *x0cp = NULL; /* init return value */ +#if 1 + /* Special case is if yc parent (yp) is choice/case + * then find x0 child with same yc even though it does not match lexically + * However this will give another y0c != yc + */ + if ((yp = yang_choice(yc)) != NULL){ + x0c = NULL; + while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL) { + if ((y0c = xml_spec(x0c)) != NULL && + (y0p = yang_choice(y0c)) != NULL && + y0p == yp) + break; /* x0c will have a value */ + } + *x0cp = x0c; + goto ok; /* What to do if not found? */ + } +#endif switch (yc->ys_keyword){ case Y_CONTAINER: /* Equal regardless */ case Y_LEAF: /* Equal regardless */ @@ -629,11 +648,12 @@ match_base_child(cxobj *x0, /* Get match. Sorting mode(optimized) or not?*/ if (xml_child_nr(x0)==0 || xml_spec(xml_child_i(x0,0))!=NULL){ yorder = yang_order(yc); - *x0cp = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval); + x0c = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval); } else{ - *x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); + x0c = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); } + *x0cp = x0c; ok: retval = 0; done: diff --git a/lib/src/clixon_xpath_parse.y b/lib/src/clixon_xpath_parse.y index b121cc2c..db672129 100644 --- a/lib/src/clixon_xpath_parse.y +++ b/lib/src/clixon_xpath_parse.y @@ -95,7 +95,7 @@ #define _YYERROR(msg) {clicon_err(OE_XML, 0, "YYERROR %s '%s' %d", (msg), clixon_xpath_parsetext, _XY->xy_linenum); YYERROR;} -/* add _yy to error paramaters */ +/* add _yy to error parameters */ #define YY_(msgid) msgid #include "clixon_config.h" diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 36404682..b618f037 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -624,6 +624,28 @@ yang_find_mynamespace(yang_stmt *ys) return namespace; } +/*! If a given yang stmt has a choice/case as parent, return the choice statement + */ +yang_node * +yang_choice(yang_stmt *y) +{ + yang_node *yp; + + if ((yp = y->ys_parent) != NULL){ + switch (yp->yn_keyword){ + case Y_CHOICE: + return yp; + break; + case Y_CASE: + return yp->yn_parent; + break; + default: + break; + } + } + return NULL; +} + /*! Find matching y in yp:s children, return 0 and index or -1 if not found. * @retval 0 not found * @retval 1 found @@ -2685,7 +2707,7 @@ yang_mandatory(yang_stmt *ys) { yang_stmt *ym; - if (ys->ys_keyword != Y_LEAF) + if (ys->ys_keyword != Y_LEAF && ys->ys_keyword != Y_CHOICE) return 0; if ((ym = yang_find((yang_node*)ys, Y_MANDATORY, NULL)) != NULL){ if (ym->ys_cv == NULL) /* shouldnt happen */ diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y index 9ffac45e..aef0cc07 100644 --- a/lib/src/clixon_yang_parse.y +++ b/lib/src/clixon_yang_parse.y @@ -165,7 +165,7 @@ #define _YYERROR(msg) {clicon_debug(2, "YYERROR %s '%s' %d", (msg), clixon_yang_parsetext, _YY->yy_linenum); YYERROR;} -/* add _yy to error paramaters */ +/* add _yy to error parameters */ #define YY_(msgid) msgid #include "clixon_config.h" diff --git a/test/test_choice.sh b/test/test_choice.sh new file mode 100755 index 00000000..c2b2f91f --- /dev/null +++ b/test/test_choice.sh @@ -0,0 +1,212 @@ +#!/bin/bash +# Choice type and mandatory +# Example from RFC7950 Sec 7.9.6 +# Also test mandatory behaviour as in 7.6.5 +# (XXX Would need default test in 7.6.4) +# Use-case: The ietf-netconf edit-config has a shorthand version of choice w mandatory: +# container { choice target { mandatory; leaf candidate; leaf running; }} + +APPNAME=example +# include err() and new() functions and creates $dir +. ./lib.sh + +cfg=$dir/choice.xml +fyang=$dir/type.yang + +cat < $cfg + + $cfg + $dir + /usr/local/share/$APPNAME/yang + /usr/local/share/clixon + system + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + false + /usr/local/var/$APPNAME/$APPNAME.pidfile + 1 + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + +EOF + +cat < $fyang +module system{ + yang-version 1.1; + namespace "urn:example:config"; + prefix ex; + container system{ + /* From RFC 7950 7.9.6 */ + container protocol { + presence true; + choice name { + case a { + leaf udp { + type empty; + } + } + case b { + leaf tcp { + type empty; + } + } + } + } + /* Same but shorthand */ + container shorthand { + presence true; + choice name { + leaf udp { + type empty; + } + leaf tcp { + type empty; + } + } + } + /* Same with mandatory true */ + container mandatory { + presence true; + choice name { + mandatory true; + case a { + leaf udp { + type empty; + } + } + case b { + leaf tcp { + type empty; + } + } + } + } + } +} +EOF + +new "test params: -f $cfg -y $fyang" + +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 -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi +fi + +new "kill old restconf daemon" +sudo pkill -u www-data clixon_restconf + +new "start restconf daemon" +sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG" -s /bin/sh www-data & + +# First vanilla (protocol) case +new "netconf validate empty" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "netconf set empty protocol" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" + +new "netconf validate protocol" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "netconf set protocol tcp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" + +new "netconf get protocol tcp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' + +new "netconf commit protocol tcp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "netconf changing from TCP to UDP (RFC7950 7.9.6)" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^]]>]]>$' + +new "netconf get protocol udp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' + +new "netconf commit protocol udp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "restconf set protocol tcp" +expecteq "$(curl -s -X PUT http://localhost/restconf/data/system:system/protocol -d {\"system:protocol\":{\"tcp\":null}})" "" + +new2 "restconf get protocol tcp" +expecteq "$(curl -s -X GET http://localhost/restconf/data/system:system)" '{"system:system": {"protocol": {"tcp": null}}} + ' + +new "cli set protocol udp" +expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set system protocol udp" 0 "^$" + +new "cli get protocol udp" +expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o show configuration cli " 0 "^system protocol udp$" + +new "cli delete all" +expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o delete all" 0 "^$" + +new "commit" +expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o commit" 0 "^$" + +# Second shorthand (no case) +new "netconf set shorthand tcp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" + +new "netconf changing from TCP to UDP (RFC7950 7.9.6)" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^]]>]]>$' + +new "netconf get shorthand udp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' + +new "netconf validate shorthand" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +# Third check mandatory +new "netconf set empty mandatory" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" + +new "netconf get mandatory empty" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' + +new "netconf validate mandatory" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationmissing-elementnameerrorMandatory choice]]>]]>$' + +new "netconf set mandatory udp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" + +new "netconf get mandatory udp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' + +new "netconf validate mandatory" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "Kill restconf daemon" +sudo pkill -u www-data -f "/www-data/clixon_restconf" + +if [ $BE -eq 0 ]; then + exit # BE +fi + +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 +sudo clixon_backend -z -f $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi + +rm -rf $dir diff --git a/test/test_leafref.sh b/test/test_leafref.sh index 63c6a270..4bb83b5c 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -88,7 +88,7 @@ if [ $BE -ne 0 ]; then fi new "leafref base config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth0ex:eth
192.0.2.1
192.0.2.2
loex:lo
127.0.0.1
]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth0ex:eth
192.0.2.124
192.0.2.224
loex:lo
127.0.0.132
]]>]]>' '^]]>]]>$' new "leafref get config" expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^eth0' diff --git a/test/test_rpc.sh b/test/test_rpc.sh index f033dfb0..45b357f1 100755 --- a/test/test_rpc.sh +++ b/test/test_rpc.sh @@ -115,13 +115,28 @@ expecteq "$(curl -s -X POST -d '{"example:input":{"x":"0","extra":"0"}}' http:// new2 "restconf wrong method" expecteq "$(curl -s -X POST -d '{"example:input":{"x":"0"}}' http://localhost/restconf/operations/example:wrong)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "wrong"},"error-severity": "error","error-message": "RPC not defined"}}} ' -new2 "restconf edit-config missing mandatory" +new2 "restconf example missing input" expecteq "$(curl -s -X POST -d '{"example:input":null}' http://localhost/restconf/operations/ietf-netconf:edit-config)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' + new "netconf kill-session missing session-id mandatory" expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationmissing-elementsession-iderrorMandatory variable]]>]]>$' -# edit-config? +new "netconf edit-config ok" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^]]>]]>$" + +new "netconf edit-config extra arg should fail" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^applicationunknown-elementextraerror]]>]]>$" + +new "netconf edit-config empty target should fail" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationmissing-elementconfig-targeterrorMandatory choice]]>]]> +$' + +new "netconf edit-config missing target should fail" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^missing-elementprotocolerrortarget]]>]]>$" + +new "netconf edit-config missing config should fail" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' 'applicationmissing-elementedit-contenterrorMandatory choice]]>]]>$' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" From 5b6af82e2989107559d020437c8f36d0880f1c81 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 5 Jan 2019 11:08:16 +0100 Subject: [PATCH 12/17] Validation of mandatory choice and recursive mandatory containers. --- CHANGELOG.md | 17 +++-- apps/backend/backend_client.c | 5 +- apps/backend/backend_commit.c | 4 +- apps/netconf/netconf_main.c | 4 +- apps/restconf/README.md | 10 +-- example/example_cli.c | 2 +- lib/src/clixon_xml_map.c | 132 ++++++++++++++++++++++------------ lib/src/clixon_yang.c | 52 +++++++++++--- test/test_choice.sh | 2 +- test/test_nacm_ext.sh | 4 +- test/test_nacm_protocol.sh | 5 +- test/test_openconfig.sh | 34 ++++++--- test/test_restconf.sh | 2 +- test/test_rpc.sh | 7 +- 14 files changed, 186 insertions(+), 94 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58ca127d..36269387 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## 3.9.0 (Preliminary Target: Mid-January 2019) ### Planned new features -* [Roadmap](ROADMAP.md) (Uncommitted and unprioritized) +* [Roadmap](ROADMAP.md) ### Major New features * Correct XML namespace handling @@ -56,11 +56,6 @@ ``` * To keep previous non-strict namespace handling (backwards compatible), set CLICON_XML_NS_STRICT to false. * See https://github.com/clicon/clixon/issues/49 -* NACM extension (RFC8341) - * NACM module support (RFC8341 A1+A2) - * Recovery user "_nacm_recovery" added. - * Example use is restconf PUT when NACM edit-config is permitted, then automatic commit and discard are permitted using recovery user. - * Example user changed adm1 to andy to comply with RFC8341 example * Yang code upgrade (RFC7950) * YANG parser cardinality checked (https://github.com/clicon/clixon/issues/48) * See https://github.com/clicon/clixon/issues/84 @@ -70,13 +65,18 @@ * Openconfig yang specs parsed: https://github.com/openconfig/public * Improved "unknown" handling * More precise Yang validation and better error messages - * Example: adding bad-, missing-, or unknown-element error messages, etc instead of operation-failed, bad-element instead of "yang node not found", etc. - * + * Example: adding bad-, missing-, or unknown-element error messages, instead of operation-failed. + * Validation of mandatory choice and recursive mandatory containers * Yang load file configure options changed * `CLICON_YANG_DIR` is changed from a single directory to a path of directories * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list * CLICON_YANG_MAIN_FILE Provides a filename with a single module filename. * CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded. +* NACM extension (RFC8341) + * NACM module support (RFC8341 A1+A2) + * Recovery user "_nacm_recovery" added. + * Example use is restconf PUT when NACM edit-config is permitted, then automatic commit and discard are permitted using recovery user. + * Example user changed adm1 to andy to comply with RFC8341 example ### API changes on existing features (you may need to change your code) * Stricter YANG choice validation leads to enforcement of structures like: `choice c{ mandatory true; leaf x` statements. `x` was not previously enforced. @@ -95,7 +95,6 @@ * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h ### Minor changes -* Yang choice functionality improved and stricter validation for CLI generation, mandatory flags, etc. * Added new clixon-lib yang module for internal netconf protocol. Currently only extends the standard with a debug RPC. * Added three-valued return values for several validate functions where -1 is fatal error, 0 is validation failed and 1 is validation OK. * This includes: `xmldb_put`, `xml_yang_validate_all`, `xml_yang_validate_add`, `xml_yang_validate_rpc`, `api_path2xml`, `api_path2xpath` diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index a141f09f..2bbfbf85 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -430,8 +430,9 @@ from_client_edit_config(clicon_handle h, goto done; } if ((target = netconf_db_find(xn, "target")) == NULL){ - clicon_err(OE_XML, 0, "db not found"); - goto done; + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + goto done; + goto ok; } if ((cbx = cbuf_new()) == NULL){ clicon_err(OE_XML, errno, "cbuf_new"); diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index d7c2d6c3..8c406d69 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -117,8 +117,8 @@ generic_validate(yang_spec *yspec, for (i=0; itd_dlen; i++){ x1 = td->td_dvec[i]; ys = xml_spec(x1); - if (ys && yang_mandatory(ys)){ - if (netconf_missing_element(cbret, "protocol", xml_name(x1), "Removed mandatory variable") < 0) + if (ys && yang_mandatory(ys) && yang_config(ys)==0){ + if (netconf_missing_element(cbret, "protocol", xml_name(x1), "Missing mandatory variable") < 0) goto done; goto fail; } diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index 7e1163dc..19ec4ee6 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -97,8 +97,8 @@ netconf_input_packet(clicon_handle h, cxobj *xa; cxobj *xa2; - clicon_debug(1, "RECV"); - clicon_debug(2, "%s: RCV: \"%s\"", __FUNCTION__, cbuf_get(cb)); + clicon_debug(1, "%s", __FUNCTION__); + clicon_debug(2, "%s: \"%s\"", __FUNCTION__, cbuf_get(cb)); if ((cbret = cbuf_new()) == NULL){ clicon_err(LOG_ERR, errno, "cbuf_new"); goto done; diff --git a/apps/restconf/README.md b/apps/restconf/README.md index 2295e3fa..e951e18a 100644 --- a/apps/restconf/README.md +++ b/apps/restconf/README.md @@ -2,10 +2,10 @@ * [Installation](#installation) * [Streams](#streams) - * [Nchan Streams](#nchan-streams) + * [Nchan Streams](#nchan) * [Debugging](#debugging) -## 1. Installation +## Installation The examples are based on Nginx. Other reverse proxies should work but are not verified. @@ -76,7 +76,7 @@ Example of writing a new interfaces specification: curl -sX PUT http://localhost/restconf/data -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth1","type":"ex:eth","enabled":true}}}' ``` -## 2. Streams +## Streams Clixon have two experimental restconf event stream implementations following RFC8040 Section 6 using SSE. One native and one using Nginx @@ -125,7 +125,7 @@ You can also specify start and stop time. Start-time enables replay of existing See (stream tests)[../test/test_streams.sh] for more examples. -## 3. Nchan +## Nchan As an alternative streams implementation, Nginx/Nchan can be used. Nginx uses pub/sub channels and can be configured in a variety of @@ -180,7 +180,7 @@ curl -H "Accept: text/event-stream" -H "Last-Event-ID: 1539961709:0" -s -X GET h See (https://nchan.io/#eventsource) on more info on how to access an SSE sub endpoint. -## 4. Debugging +## Debugging Start the restconf fastcgi program with debug flag: ``` diff --git a/example/example_cli.c b/example/example_cli.c index bf24064d..ebd2305a 100644 --- a/example/example_cli.c +++ b/example/example_cli.c @@ -96,7 +96,7 @@ fib_route_rpc(clicon_handle h, /* User supplied variable in CLI command */ instance = cvec_find(cvv, "instance"); /* get a cligen variable from vector */ /* Create XML for fib-route netconf RPC */ - if (xml_parse_va(&xtop, NULL, "%s", + if (xml_parse_va(&xtop, NULL, "%sipv4", clicon_username_get(h), cv_string_get(instance)) < 0) goto done; diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 4b51e30b..faac0666 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -482,6 +482,89 @@ xml_yang_validate_rpc(cxobj *xrpc, goto done; } +/*! Check if an xml node lacks mandatory children + * @param[in] xt XML node to be validated + * @param[in] yt xt:s yang statement + * @param[out] cbret Error buffer (set w netconf error if retval == 0) + * @retval 1 Validation OK + * @retval 0 Validation failed (cbret set) + * @retval -1 Error + */ +static int +check_mandatory(cxobj *xt, + yang_stmt *yt, + cbuf *cbret) +{ + int retval = -1; + int i; + cxobj *x; + yang_stmt *y; + yang_stmt *yc; + yang_node *yp; + + for (i=0; iys_len; i++){ + yc = yt->ys_stmt[i]; + if (!yang_mandatory(yc)) + continue; + switch (yc->ys_keyword){ + case Y_CONTAINER: + case Y_ANYDATA: + case Y_ANYXML: + case Y_LEAF: + if (yang_config(yc)==0) + break; + /* Find a child with the mandatory yang */ + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if ((y = xml_spec(x)) != NULL + && y==yc) + break; /* got it */ + } + if (x == NULL){ + if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory variable") < 0) + goto done; + goto fail; + } + break; + case Y_CHOICE: /* More complex because of choice/case structure */ + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if ((y = xml_spec(x)) != NULL && + (yp = yang_choice(y)) != NULL && + yp == (yang_node*)yc){ + break; /* leave loop with x set */ + } + } + if (x == NULL){ + /* @see RFC7950: 15.6 Error Message for Data That Violates + * a Mandatory "choice" Statement */ + if (cprintf(cbret, "" + "application" + "data-missing" + "missing-choice" +#ifdef NYI + // "" +#endif + "%s" + "error" + "", + yc->ys_argument) <0) + goto done; + goto fail; + } + break; + default: + break; + } /* switch */ + } + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + /*! Validate a single XML node with yang specification for added entry * 1. Check if mandatory leafs present as subs. * 2. Check leaf values, eg int ranges and string regexps. @@ -510,10 +593,6 @@ xml_yang_validate_add(cxobj *xt, cg_var *cv = NULL; char *reason = NULL; yang_stmt *yt; /* yang spec of xt going in */ - yang_stmt *yc; /* yang spec of yt child */ - yang_stmt *yx; /* yang spec of xt children */ - yang_node *yp; /* yang spec of parent of yang spec of xt children */ - int i; char *body; int ret; cxobj *x; @@ -521,47 +600,12 @@ xml_yang_validate_add(cxobj *xt, /* if not given by argument (overide) use default link and !Node has a config sub-statement and it is false */ if ((yt = xml_spec(xt)) != NULL && yang_config(yt) != 0){ + if ((ret = check_mandatory(xt, yt, cbret)) < 0) + goto done; + if (ret == 0) + goto fail; + /* Check leaf values */ switch (yt->ys_keyword){ - case Y_RPC: - case Y_INPUT: - case Y_LIST: - /* fall thru */ - case Y_CONTAINER: - for (i=0; iys_len; i++){ - yc = yt->ys_stmt[i]; - switch (yc->ys_keyword){ - case Y_LEAF: - if (yang_config(yc)==0) - break; - if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){ - if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory variable") < 0) - goto done; - goto fail; - } - break; - case Y_CHOICE: - if (yang_mandatory(yc)){ - /* If there is one child with this choice as parent */ - x = NULL; - while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { - if ((yx = xml_spec(x)) != NULL && - (yp = yang_choice(yx)) != NULL && - yp == (yang_node*)yc){ - break; /* leave loop with x set */ - } - } - if (x == NULL){ /* No hit */ - if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory choice") < 0) - goto done; - goto fail; - } - } - break; - default: - break; - } - } - break; case Y_LEAF: /* fall thru */ case Y_LEAF_LIST: diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index b618f037..7a81bc6b 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -2701,26 +2701,62 @@ ys_parse_sub(yang_stmt *ys, * Note: one can cache this value in ys_cvec instead of functionally evaluating it. * @retval 1 yang statement is leaf and it has a mandatory sub-stmt with value true * @retval 0 The negation of conditions for return value 1. + * @see RFC7950 Sec 3: + * o mandatory node: A mandatory node is one of: + * 1) A leaf, choice, anydata, or anyxml node with a "mandatory" + * statement with the value "true". + * 2) A list or leaf-list node with a "min-elements" statement with a + * value greater than zero. + * 3) A container node without a "presence" statement and that has at + * least one mandatory node as a child. */ int yang_mandatory(yang_stmt *ys) { yang_stmt *ym; + char *reason = NULL; + uint8_t min_elements; /* XXX change to 32 (need new cligen version) */ - if (ys->ys_keyword != Y_LEAF && ys->ys_keyword != Y_CHOICE) - return 0; - if ((ym = yang_find((yang_node*)ys, Y_MANDATORY, NULL)) != NULL){ - if (ym->ys_cv == NULL) /* shouldnt happen */ - return 0; - return cv_bool_get(ym->ys_cv); + /* 1) A leaf, choice, anydata, or anyxml node with a "mandatory" + * statement with the value "true". */ + if (ys->ys_keyword == Y_LEAF || ys->ys_keyword == Y_CHOICE || + ys->ys_keyword == Y_ANYDATA || ys->ys_keyword == Y_ANYXML){ + if ((ym = yang_find((yang_node*)ys, Y_MANDATORY, NULL)) != NULL){ + if (ym->ys_cv != NULL) /* shouldnt happen */ + return cv_bool_get(ym->ys_cv); + } + } + /* 2) A list or leaf-list node with a "min-elements" statement with a + * value greater than zero. */ + else if (ys->ys_keyword == Y_LIST || ys->ys_keyword == Y_LEAF_LIST){ + if ((ym = yang_find((yang_node*)ys, Y_MIN_ELEMENTS, NULL)) != NULL){ + /* XXX change to 32 (need new cligen version) */ + if (parse_uint8(ym->ys_argument, &min_elements, &reason) != 1){ + clicon_err(OE_YANG, EINVAL, "%s", reason?reason:"parse_uint8"); + return 0; /* XXX ignore error */ + } + return min_elements > 0; + } + } + /* 3) A container node without a "presence" statement and that has at + * least one mandatory node as a child. */ + else if (ys->ys_keyword == Y_CONTAINER && + yang_find((yang_node*)ys, Y_PRESENCE, NULL) == NULL){ + yang_stmt *yc; + int i; + for (i=0; iys_len; i++){ + yc = ys->ys_stmt[i]; + if (yang_mandatory(yc)) + return 1; + } } return 0; } /*! Return config state of this node * config statement is default true. - * Note that a node with config=false may not have a sub - * statement where config=true. And this function does not check the sttaus of a parent. + * @note a node with config=false may not have a sub statement where + * config=true. And this function does not check the status of a parent. * @retval 0 if node has a config sub-statement and it is false * @retval 1 node has not config sub-statement or it is true */ diff --git a/test/test_choice.sh b/test/test_choice.sh index c2b2f91f..1289a0f5 100755 --- a/test/test_choice.sh +++ b/test/test_choice.sh @@ -179,7 +179,7 @@ new "netconf get mandatory empty" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' new "netconf validate mandatory" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationmissing-elementnameerrorMandatory choice]]>]]>$' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationdata-missingmissing-choicenameerror]]>]]>$' new "netconf set mandatory udp" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index b9cef0e1..edbef77f 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -137,8 +137,8 @@ EOF new "test params: -f $cfg -y $fyang" if [ $BE -ne 0 ]; then - new "kill old backend -zf $cfg -y $fyang" - sudo clixon_backend -zf $cfg -y $fyang + new "kill old backend -zf $cfg " + sudo clixon_backend -zf $cfg if [ $? -ne 0 ]; then err fi diff --git a/test/test_nacm_protocol.sh b/test/test_nacm_protocol.sh index b4e5bc2f..8a3c298e 100755 --- a/test/test_nacm_protocol.sh +++ b/test/test_nacm_protocol.sh @@ -21,7 +21,7 @@ # automatically committed to running immediately after each successful # edit. # Which means that restconf -X DELETE /data translates to edit-config + commit -# WHICH IS allowed. +# which is allowed. APPNAME=example # include err() and new() functions and creates $dir @@ -65,7 +65,6 @@ module $APPNAME{ } } EOF - # The groups are slightly modified from RFC8341 A.1 # The rule-list is from A.2 RULES=$(cat < $cfg EOF files=$(find $OPENCONFIG -name "*.yang") -# Just cound nr of modules (exclude submodule) -let m=0; # Nr of modules +# Count nr of modules (exclude submodule) Assume "module" or "submodule" +# first word on first line +let ms=0; # Nr of modules +let ss=0; # Nr of smodules for f in $files; do - if [ -n "$(head -1 $f|grep '^module')" ]; then + let m=0; # Nr of modules + let s=0; # Nr of modules + if [ -n "$(head -15 $f|grep '^[ ]*module')" ]; then let m++; + let ms++; + elif [ -n "$(head -15 $f|grep '^[ ]*submodule')" ]; then + let s++; + let ss++; + else + echo "No module or submodule found $f" + exit + fi + if [ $m -eq 1 -a $s -eq 1 ]; then + echo "Double match $f" + exit fi done +echo "m:$ms s:$ss" new "Openconfig test: $clixon_cli -1f $cfg -y $f show version ($m modules)" for f in $files; do if [ -n "$(head -1 $f|grep '^module')" ]; then - new "cli $f" + new "$clixon_cli -1f $cfg -y $f show version" expectfn "$clixon_cli -1f $cfg -y $f show version" 0 "3." fi diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 2a1fd961..66710e91 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -293,7 +293,7 @@ new2 "restconf rpc missing input" expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": "restconf RPC does not have input statement"}}} ' new "restconf rpc using POST xml" -ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"ietf-routing:input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route) +ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"ietf-routing:input":{"routing-instance-name":"ipv4","destination-address":{"address-family":"ipv4"}}}' http://localhost/restconf/operations/ietf-routing:fib-route) expect='ipv42.3.4.5static' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then diff --git a/test/test_rpc.sh b/test/test_rpc.sh index 45b357f1..27fb74c8 100755 --- a/test/test_rpc.sh +++ b/test/test_rpc.sh @@ -129,14 +129,13 @@ new "netconf edit-config extra arg should fail" expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^applicationunknown-elementextraerror]]>]]>$" new "netconf edit-config empty target should fail" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationmissing-elementconfig-targeterrorMandatory choice]]>]]> -$' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationdata-missingmissing-choiceconfig-targeterror]]>]]>$' new "netconf edit-config missing target should fail" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^missing-elementprotocolerrortarget]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationmissing-elementtargeterrorMandatory variable]]>]]>$' new "netconf edit-config missing config should fail" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' 'applicationmissing-elementedit-contenterrorMandatory choice]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationdata-missingmissing-choiceedit-contenterror]]>]]>$' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" From 0502ca4218a3fcd188d3512f7a34e066d2163304 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 5 Jan 2019 14:16:56 +0100 Subject: [PATCH 13/17] Hand-crafted validation messages removed and replaced with generic validations. --- CHANGELOG.md | 4 +- apps/netconf/netconf_rpc.c | 390 +++---------------------------------- 2 files changed, 30 insertions(+), 364 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36269387..937eeac7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,9 +80,9 @@ ### API changes on existing features (you may need to change your code) * Stricter YANG choice validation leads to enforcement of structures like: `choice c{ mandatory true; leaf x` statements. `x` was not previously enforced. -* CLICON_XML_SORT option (in clixon-config.yang) has been removed and set to true permanently since setting it to false is obsolete. +* Many hand-crafted validation messages have been removed and replaced with generic validations, which may lead to changed rpc-error messages. +* CLICON_XML_SORT option (in clixon-config.yang) has been removed and set to true permanently. Unsorted XML lists leads to slower performance and old obsolete code can be removed. * Strict namespace setting can be a problem when upgrading existing database files, such as startup-db or persistent running-db, or any other saved XML file. - * For backward compatibility, load of startup and running set CLICON_XML_NS_STRICT to false temporarily. * Removed `delete-config` support for candidate db since it is not supported in RFC6241. * Switched the order of `error-type` and `error-tag` in all netconf and restconf error messages to comply to RFC order. * Yang parser is stricter (see above) which may break parsing of existing yang specs. diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 21a02193..ba41648a 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -135,20 +135,10 @@ netconf_get_config(clicon_handle h, { cxobj *xfilter; /* filter */ int retval = -1; - char *source; char *ftype = NULL; cxobj *xfilterconf; cxobj *xconf; - if ((source = netconf_get_target(xn, "source")) == NULL){ - xml_parse_va(xret, NULL, "" - "missing-element" - "protocol" - "error" - "source" - ""); - goto ok; - } /* ie ... */ if ((xfilter = xpath_first(xn, "filter")) != NULL) ftype = xml_find_value(xfilter, "type"); @@ -187,7 +177,6 @@ netconf_get_config(clicon_handle h, "type" "
"); } - ok: /* netconf error is not fatal */ retval = 0; done: return retval; @@ -223,11 +212,9 @@ get_edit_opts(cxobj *xn, if ((optstr = xml_body(x)) != NULL){ if (strcmp(optstr, "test-then-set") == 0) *testopt = TEST_THEN_SET; - else - if (strcmp(optstr, "set") == 0) + else if (strcmp(optstr, "set") == 0) *testopt = SET; - else - if (strcmp(optstr, "test-only") == 0) + else if (strcmp(optstr, "test-only") == 0) *testopt = TEST_ONLY; else goto parerr; @@ -311,52 +298,18 @@ netconf_edit_config(clicon_handle h, { int retval = -1; int optret; - enum operation_type operation = OP_MERGE; enum test_option testopt = TEST_THEN_SET;/* only supports this */ enum error_option erropt = STOP_ON_ERROR; /* only supports this */ - cxobj *x; - cxobj *xfilter; - char *ftype = NULL; - char *target; /* db */ - /* must have target, and it should be candidate */ - if ((target = netconf_get_target(xn, "target")) == NULL || - strcmp(target, "candidate")){ - xml_parse_va(xret, NULL, "" - "missing-element" - "protocol" - "error" - "target" - ""); - goto ok; - } - /* CLICON addition, eg /> */ - if ((xfilter = xpath_first(xn, "filter")) != NULL) { - if ((ftype = xml_find_value(xfilter, "type")) != NULL) - if (strcmp(ftype,"restconf")){ - xml_parse_va(xret, NULL, "" - "invalid-value" - "protocol" - "error" - ""); - goto ok; - } - } - if ((x = xpath_first(xn, "default-operation")) != NULL){ - if (xml_operation(xml_body(x), &operation) < 0){ - xml_parse_va(xret, NULL, "" - "invalid-value" - "protocol" - "error" - ""); - goto ok; - } - } if ((optret = get_edit_opts(xn, &testopt, &erropt, xret)) < 0) goto done; if (optret == 0) /* error in opt parameters */ goto ok; - /* not supported opts */ + /* These constraints are clixon-specific since :validate should + * support all testopts, and erropts should be supported + * And therefore extends the validation + * (implement the features before removing these checks) + */ if (testopt!=TEST_THEN_SET || erropt!=STOP_ON_ERROR){ xml_parse_va(xret, NULL, "" "operation-not-supported" @@ -364,158 +317,15 @@ netconf_edit_config(clicon_handle h, "error" ""); goto ok; - } - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - ok: - retval = 0; - done: - return retval; -} - -/*! Netconf copy configuration - - - - - - - - - - - * @param[in] h clicon handle - * @param[in] xn Sub-tree (under xorig) at ... level. - * @param[out] xret Return XML, error or OK - */ -static int -netconf_copy_config(clicon_handle h, - cxobj *xn, - cxobj **xret) -{ - int retval = -1; - char *source; - char *target; /* filenames */ - - if ((source = netconf_get_target(xn, "source")) == NULL){ - xml_parse_va(xret, NULL, "" - "missing-element" - "protocol" - "error" - "source" - ""); - goto ok; - } - if ((target = netconf_get_target(xn, "target")) == NULL){ - xml_parse_va(xret, NULL, "" - "missing-element" - "protocol" - "error" - "target" - ""); - goto ok; - } - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - ok: - retval = 0; - done: - return retval; -} - -/*! Delete configuration - - - - - - Delete a configuration datastore. The - configuration datastore cannot be deleted. - * @param[in] h clicon handle - * @param[in] xn Sub-tree (under xorig) at ... level. - * @param[out] xret Return XML, error or OK - */ -static int -netconf_delete_config(clicon_handle h, - cxobj *xn, - cxobj **xret) -{ - char *target; /* filenames */ - int retval = -1; - - if ((target = netconf_get_target(xn, "target")) == NULL || - strcmp(target, "running")==0){ - xml_parse_va(xret, NULL, "" - "missing-element" - "protocol" - "error" - "target" - ""); - goto ok; - } - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - ok: - retval = 0; - done: - return retval; -} - - -/*! Lock a database - - - - - - * @param[in] h clicon handle - * @param[in] xn Sub-tree (under xorig) at ... level. - * @param[out] xret Return XML, error or OK - */ -static int -netconf_lock(clicon_handle h, - cxobj *xn, - cxobj **xret) -{ - int retval = -1; - char *target; - - if ((target = netconf_get_target(xn, "target")) == NULL){ - xml_parse_va(xret, NULL, "" - "missing-element" - "protocol" - "error" - "target" - ""); - goto ok; } if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) goto done; ok: retval = 0; - done: + done: return retval; } -/*! Unlock a database - - - - - - XXX - * @param[in] h clicon handle - * @param[in] xn Sub-tree (under xorig) at ... level. - * @param[out] xret Return XML, error or OK - */ -static int -netconf_unlock(clicon_handle h, - cxobj *xn, - cxobj **xret) -{ - return netconf_lock(h, xn, xret); -} - /*! Get running configuration and device state information * * @@ -579,138 +389,11 @@ netconf_get(clicon_handle h, "type" "
"); } - // ok: /* netconf error is not fatal */ retval = 0; done: return retval; } - -/*! Close a (user) session - - * @param[in] xn Sub-tree (under xorig) at ... level. - * @param[out] xret Return XML, error or OK -*/ -static int -netconf_close_session(clicon_handle h, - cxobj *xn, - cxobj **xret) -{ - int retval = -1; - - cc_closed++; - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - retval = 0; - done: - return retval; -} - -/*! Kill other user sessions - - PID - - * @param[in] xn Sub-tree (under xorig) at ... level. - * @param[out] xret Return XML, error or OK - */ -static int -netconf_kill_session(clicon_handle h, - cxobj *xn, - cxobj **xret) -{ - int retval=-1; - cxobj *xs; - - if ((xs = xpath_first(xn, "//session-id")) == NULL){ - xml_parse_va(xret, NULL, "" - "missing-element" - "protocol" - "error" - "session-id" - ""); - goto ok; - } - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - ok: - retval = 0; - done: - return retval; -} -/*! Check the semantic consistency of candidate - - :validate - * @param[in] h clicon handle - * @param[in] xn Sub-tree (under xorig) at ... level. - * @param[out] xret Return XML, error or OK - */ -static int -netconf_validate(clicon_handle h, - cxobj *xn, - cxobj **xret) -{ - int retval = -1; - char *target; - - if ((target = netconf_get_target(xn, "source")) == NULL){ - xml_parse_va(xret, NULL, "" - "missing-element" - "protocol" - "error" - "target" - ""); - goto ok; - } - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - ok: - retval = 0; - done: - return retval; -} - -/*! Commit candidate -> running - - :candidate - * @param[in] h clicon handle - * @param[in] xn Sub-tree (under xorig) at ... level. - * @param[out] xret Return XML, error or OK - */ -static int -netconf_commit(clicon_handle h, - cxobj *xn, - cxobj **xret) -{ - int retval = -1; - - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - retval = 0; - done: - return retval; -} - -/*! Discard all changes in candidate / revert to running - - :candidate - * @param[in] h clicon handle - * @param[in] xn Sub-tree (under xorig) at ... level. - * @param[out] xret Return XML, error or OK - */ -static int -netconf_discard_changes(clicon_handle h, - cxobj *xn, - cxobj **xret) -{ - int retval = -1; - - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - retval = 0; - done: - return retval; -} - /*! Called when a notification has happened on backend * and this session has registered for that event. * Filter it and forward it. @@ -993,9 +676,25 @@ netconf_rpc_dispatch(clicon_handle h, if (xml_value_set(xa, username) < 0) goto done; } + /* Many of these calls are now calling generic clicon_rpc_netconf_xml + * directly, since the validation is generic and done before this place + * in the call. Some call however need extra validation, such as the + * filter parameter to get/get-config and tes- err-opts of edit-config. + */ xe = NULL; while ((xe = xml_child_each(xn, xe, CX_ELMNT)) != NULL) { - if (strcmp(xml_name(xe), "get-config") == 0){ + if (strcmp(xml_name(xe), "copy-config") == 0 || + strcmp(xml_name(xe), "delete-config") == 0 || + strcmp(xml_name(xe), "lock") == 0 || + strcmp(xml_name(xe), "unlock") == 0 || + strcmp(xml_name(xe), "kill-session") == 0 || + strcmp(xml_name(xe), "validate") == 0 || /* :validate */ + strcmp(xml_name(xe), "commit") == 0 || /* :candidate */ + strcmp(xml_name(xe), "discard-changes") == 0){ + if (clicon_rpc_netconf_xml(h, xml_parent(xe), xret, NULL) < 0) + goto done; + } + else if (strcmp(xml_name(xe), "get-config") == 0){ if (netconf_get_config(h, xe, xret) < 0) goto done; } @@ -1003,47 +702,14 @@ netconf_rpc_dispatch(clicon_handle h, if (netconf_edit_config(h, xe, xret) < 0) goto done; } - else if (strcmp(xml_name(xe), "copy-config") == 0){ - if (netconf_copy_config(h, xe, xret) < 0) - goto done; - } - else if (strcmp(xml_name(xe), "delete-config") == 0){ - if (netconf_delete_config(h, xe, xret) < 0) - goto done; - } - else if (strcmp(xml_name(xe), "lock") == 0) { - if (netconf_lock(h, xe, xret) < 0) - goto done; - } - else if (strcmp(xml_name(xe), "unlock") == 0){ - if (netconf_unlock(h, xe, xret) < 0) - goto done; - } else if (strcmp(xml_name(xe), "get") == 0){ if (netconf_get(h, xe, xret) < 0) goto done; } else if (strcmp(xml_name(xe), "close-session") == 0){ - if (netconf_close_session(h, xe, xret) < 0) - goto done; - } - else if (strcmp(xml_name(xe), "kill-session") == 0) { - if (netconf_kill_session(h, xe, xret) < 0) - goto done; - } - /* Validate capability :validate */ - else if (strcmp(xml_name(xe), "validate") == 0){ - if (netconf_validate(h, xe, xret) < 0) - goto done; - } - /* Candidate configuration capability :candidate */ - else if (strcmp(xml_name(xe), "commit") == 0){ - if (netconf_commit(h, xe, xret) < 0) - goto done; - } - else if (strcmp(xml_name(xe), "discard-changes") == 0){ - if (netconf_discard_changes(h, xe, xret) < 0) - goto done; + cc_closed++; + if (clicon_rpc_netconf_xml(h, xml_parent(xe), xret, NULL) < 0) + goto done; } /* RFC 5277 :notification */ else if (strcmp(xml_name(xe), "create-subscription") == 0){ From bd67a2a5f264e106086533c071a8022ae6ad8f5b Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 6 Jan 2019 16:31:36 +0100 Subject: [PATCH 14/17] Support for empty yang string added, eg `default "";` if-feature added in yang parser at several places. --- CHANGELOG.md | 1 + lib/src/clixon_yang_parse.l | 2 +- lib/src/clixon_yang_parse.y | 23 +++++++++++++++++------ test/lib.sh | 8 ++++++++ test/test_openconfig.sh | 23 +++++++++-------------- 5 files changed, 36 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 937eeac7..d0728483 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,6 +112,7 @@ * \{ { BEGIN(KEYWORD); return *yytext; } . { return *yytext; } -[0-9][0-9]* { clixon_yang_parselval.string = strdup(yytext); +\-?[0-9][0-9]* { clixon_yang_parselval.string = strdup(yytext); return INT; } ; { BEGIN(KEYWORD); return *yytext; } \{ { BEGIN(KEYWORD); return *yytext; } diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y index aef0cc07..31fee4c8 100644 --- a/lib/src/clixon_yang_parse.y +++ b/lib/src/clixon_yang_parse.y @@ -641,7 +641,8 @@ identity_substmts : identity_substmts identity_substmt { clicon_debug(2,"identity-substmts -> identity-substmt"); } ; -identity_substmt : base_stmt { clicon_debug(2,"identity-substmt -> base-stmt"); } +identity_substmt : if_feature_stmt { clicon_debug(2,"identity-substmt -> if-feature-stmt"); } + | base_stmt { clicon_debug(2,"identity-substmt -> base-stmt"); } | status_stmt { clicon_debug(2,"identity-substmt -> status-stmt"); } | description_stmt { clicon_debug(2,"identity-substmt -> description-stmt"); } | reference_stmt { clicon_debug(2,"identity-substmt -> reference-stmt"); } @@ -876,7 +877,8 @@ enum_substmts : enum_substmts enum_substmt { clicon_debug(2,"enum-substmts -> enum-substmt"); } ; -enum_substmt : value_stmt { clicon_debug(2,"enum-substmt -> value-stmt"); } +enum_substmt : if_feature_stmt { clicon_debug(2,"enum-substmt -> if-feature-stmt"); } + | value_stmt { clicon_debug(2,"enum-substmt -> value-stmt"); } | status_stmt { clicon_debug(2,"enum-substmt -> status-stmt"); } | description_stmt { clicon_debug(2,"enum-substmt -> description-stmt"); } | reference_stmt { clicon_debug(2,"enum-substmt -> reference-stmt"); } @@ -911,7 +913,8 @@ bit_substmts : bit_substmts bit_substmt { clicon_debug(2,"bit-substmts -> bit-substmt"); } ; -bit_substmt : position_stmt { clicon_debug(2,"bit-substmt -> positition-stmt"); } +bit_substmt : if_feature_stmt { clicon_debug(2,"bit-substmt -> if-feature-stmt"); } + | position_stmt { clicon_debug(2,"bit-substmt -> positition-stmt"); } | status_stmt { clicon_debug(2,"bit-substmt -> status-stmt"); } | description_stmt { clicon_debug(2,"bit-substmt -> description-stmt"); } | reference_stmt { clicon_debug(2,"bit-substmt -> reference-stmt"); } @@ -1318,9 +1321,16 @@ refine_substmts : refine_substmts refine_substmt { clicon_debug(2,"refine-substmts -> refine-substmt"); } ; -refine_substmt : must_stmt { clicon_debug(2,"refine-substmt -> must-stmt"); } - | mandatory_stmt { clicon_debug(2,"refine-substmt -> mandatory-stmt"); } +refine_substmt : if_feature_stmt { clicon_debug(2,"refine-substmt -> if-feature-stmt"); } + | must_stmt { clicon_debug(2,"refine-substmt -> must-stmt"); } + | presence_stmt { clicon_debug(2,"refine-substmt -> presence-stmt"); } | default_stmt { clicon_debug(2,"refine-substmt -> default-stmt"); } + | config_stmt { clicon_debug(2,"refine-substmt -> config-stmt"); } + | mandatory_stmt { clicon_debug(2,"refine-substmt -> mandatory-stmt"); } + | min_elements_stmt { clicon_debug(2,"refine-substmt -> min-elements-stmt"); } + | max_elements_stmt { clicon_debug(2,"refine-substmt -> max-elements-stmt"); } + | description_stmt { clicon_debug(2,"refine-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"refine-substmt -> reference-stmt"); } | unknown_stmt { clicon_debug(2,"refine-substmt -> unknown-stmt");} | { clicon_debug(2,"refine-substmt -> "); } ; @@ -1561,7 +1571,8 @@ qstrings : qstrings '+' qstring { $$=$1; clicon_debug(2,"qstrings-> qstring"); } ; -qstring : DQ ustring DQ { $$=$2; clicon_debug(2,"string-> \" ustring \"");} +qstring : DQ ustring DQ { $$=$2; clicon_debug(2,"string-> \" ustring \"");} + | DQ DQ { $$=strdup(""); clicon_debug(2,"string-> \" \"");} | SQ ustring SQ { $$=$2; clicon_debug(2,"string-> ' ustring '"); } ; diff --git a/test/lib.sh b/test/lib.sh index 3d82e32d..be102a62 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -13,6 +13,14 @@ testname= # If set, enable debugging (of backend) : ${DBG:=0} +# Parse yangmodels from https://github.com/YangModels/yang +# Recommended: checkout yangmodels elsewhere in the tree and set the env +# to that +: ${YANGMODELS=$(pwd)/yang} + +# Parse yang openconfig models from https://github.com/openconfig/public +: ${OPENCONFIG=$(pwd)/public} + # For memcheck #clixon_cli="valgrind --leak-check=full --show-leak-kinds=all clixon_cli" clixon_cli=clixon_cli diff --git a/test/test_openconfig.sh b/test/test_openconfig.sh index f59333e2..47574fa2 100755 --- a/test/test_openconfig.sh +++ b/test/test_openconfig.sh @@ -6,29 +6,24 @@ # - release/models/wifi/types/openconfig-wifi-types.yang # issue: https://github.com/clicon/clixon/issues/59 # -OPENCONFIG=public -OCDIR=$OPENCONFIG/release/models - -# Clone openconfig dir if not there -if [ ! -d public ]; then - git clone https://github.com/openconfig/public -#else -# (cd public; -# #git pull -# ) -fi - -# include err() and new() functions and creates $dir -. ./lib.sh # Yang specifics: multi-keys and empty type APPNAME=example + # include err() and new() functions and creates $dir . ./lib.sh cfg=$dir/conf_yang.xml fyang=$dir/test.yang +new "openconfig" +if [ ! -d "$OPENCONFIG" ]; then + err "Hmm Openconfig dir does not seem to exist, try git clone https://github.com/openconfig/public?" +fi + +OCDIR=$OPENCONFIG/release/models + + cat < $cfg $cfg From c7e847cd247f1324b6f566f8d546872209cd2fc9 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 6 Jan 2019 22:11:59 +0100 Subject: [PATCH 15/17] Keyword "min" (not only "max") can be used in built-in types "range" and "length" statements. --- .gitignore | 2 +- CHANGELOG.md | 1 + README.md | 3 +- apps/cli/cli_generate.c | 95 +++------ lib/clixon/clixon_string.h | 1 + lib/clixon/clixon_yang.h | 3 +- lib/clixon/clixon_yang_type.h | 19 +- lib/src/clixon_string.c | 42 +++- lib/src/clixon_xml_map.c | 2 +- lib/src/clixon_yang.c | 210 +++++++++++++------- lib/src/clixon_yang_type.c | 353 +++++++++++++++------------------- test/README.md | 4 +- test/test_openconfig.sh | 3 - test/test_type.sh | 136 +++++++++---- 14 files changed, 469 insertions(+), 405 deletions(-) diff --git a/.gitignore b/.gitignore index c04c1244..1d8e315b 100644 --- a/.gitignore +++ b/.gitignore @@ -44,5 +44,5 @@ build-root/*.tar.xz build-root/*.rpm build-root/rpmbuild -test/public +test/site.sh doc/html \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index d0728483..b64cdd5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,6 +112,7 @@ * Fix the original error in cvtype_max2str - * by adding a fraction_digits argument. - */ -static char * -cvtype_max2str_dup2(enum cv_type type, - int fraction_digits) -{ - int len; - char *str; - - if (type!=CGV_DEC64 || fraction_digits==0) - return cvtype_max2str_dup(type); - if ((len = cvtype_max2str(type, NULL, 0)) < 0) - return NULL; - if ((str = (char *)malloc(len+1)) == NULL) - return NULL; - memset(str, '\0', len+1); - len = snprintf(str, len+1, "%" PRId64 ".0", (INT64_MAX/((int)pow(10,fraction_digits)))); - return str; -} - /*! Generate CLI code for Yang leaf statement to CLIgen variable of specific type * Check for completion (of already existent values), ranges (eg range[min:max]) and * patterns, (eg regexp:"[0.9]*"). @@ -208,18 +186,17 @@ yang2cli_var_sub(clicon_handle h, char *helptext, enum cv_type cvtype, int options, - cg_var *mincv, - cg_var *maxcv, + cvec *cvv, char *pattern, uint8_t fraction_digits ) { int retval = -1; char *type; - char *r; yang_stmt *yi = NULL; int i = 0; char *cvtypestr; + cg_var *cv; if (cvtype == CGV_VOID){ retval = 0; @@ -276,44 +253,30 @@ yang2cli_var_sub(clicon_handle h, if (options & YANG_OPTIONS_FRACTION_DIGITS) cprintf(cb, " fraction-digits:%u", fraction_digits); + if (options & (YANG_OPTIONS_RANGE|YANG_OPTIONS_LENGTH)){ - assert(mincv || maxcv); - cprintf(cb, " %s[", (options&YANG_OPTIONS_RANGE)?"range":"length"); - if (mincv){ - if ((r = cv2str_dup(mincv)) == NULL){ - clicon_err(OE_UNIX, errno, "cv2str_dup"); - goto done; - } - cprintf(cb, "%s:", r); - free(r); - r = NULL; - } - if (maxcv != NULL){ - if ((r = cv2str_dup(maxcv)) == NULL){ - clicon_err(OE_UNIX, errno, "cv2str_dup"); - goto done; - } - } - else{ /* Cligen does not have 'max' keyword in range so need to find actual - max value of type if yang range expression is 0..max - */ - if (cvtype==CGV_STRING){ - if ((r = malloc(512)) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); - goto done; - } - snprintf(r, 512, "%d", MAXPATHLEN); - } - else { - if ((r = cvtype_max2str_dup2(cvtype, fraction_digits)) == NULL){ - clicon_err(OE_UNIX, errno, "cvtype_max2str"); - goto done; + /* Loop through range_min and range_min..rang_max */ + i = 0; + while (iys_argument); + cv = cvec_i(cvv, i++); + if (strcmp(cv_name_get(cv),"range_min") == 0){ + cprintf(cb, " %s[", (options&YANG_OPTIONS_RANGE)?"range":"length"); + cv2cbuf(cv, cb); + cprintf(cb,":"); + /* probe next */ + if (iys_argument:NULL; @@ -370,7 +332,7 @@ yang2cli_var_union_one(clicon_handle h, if (clicon_type2cv(origtype, restype, &cvtype) < 0) goto done; if ((retval = yang2cli_var_sub(h, ys, ytype, cb, helptext, cvtype, - options, mincv, maxcv, pattern, fraction_digits)) < 0) + options, cvv, pattern, fraction_digits)) < 0) goto done; } retval = 0; @@ -438,8 +400,7 @@ yang2cli_var(clicon_handle h, char *origtype; yang_stmt *yrestype; /* resolved type */ char *restype; /* resolved type */ - cg_var *mincv = NULL; - cg_var *maxcv = NULL; + cvec *cvv = NULL; char *pattern = NULL; uint8_t fraction_digits = 0; enum cv_type cvtype; @@ -448,7 +409,7 @@ yang2cli_var(clicon_handle h, char *type; if (yang_type_get(ys, &origtype, &yrestype, - &options, &mincv, &maxcv, &pattern, &fraction_digits) < 0) + &options, &cvv, &pattern, &fraction_digits) < 0) goto done; restype = yrestype?yrestype->ys_argument:NULL; @@ -485,7 +446,7 @@ yang2cli_var(clicon_handle h, if (completionp) cprintf(cb, "("); if ((retval = yang2cli_var_sub(h, ys, yrestype, cb, helptext, cvtype, - options, mincv, maxcv, pattern, fraction_digits)) < 0) + options, cvv, pattern, fraction_digits)) < 0) goto done; if (completionp){ if (cli_expand_var_generate(h, ys, cvtype, cb, diff --git a/lib/clixon/clixon_string.h b/lib/clixon/clixon_string.h index 1b2b20fc..5895401d 100644 --- a/lib/clixon/clixon_string.h +++ b/lib/clixon/clixon_string.h @@ -87,6 +87,7 @@ int uri_percent_decode(char *enc, char **str); const char *clicon_int2str(const map_str2int *mstab, int i); int clicon_str2int(const map_str2int *mstab, char *str); int nodeid_split(char *nodeid, char **prefix, char **id); +char *clixon_trim(char *str); #ifndef HAVE_STRNDUP char *clicon_strndup (const char *, size_t); #endif /* ! HAVE_STRNDUP */ diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index b0635385..3842b225 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -176,8 +176,7 @@ typedef struct yang_stmt yang_stmt; /* forward */ */ struct yang_type_cache{ int yc_options; - cg_var *yc_mincv; - cg_var *yc_maxcv; + cvec *yc_cvv; /* range and length restriction */ char *yc_pattern; uint8_t yc_fraction; yang_stmt *yc_resolved; /* Resolved type object, can be NULL - note direct ptr */ diff --git a/lib/clixon/clixon_yang_type.h b/lib/clixon/clixon_yang_type.h index 8fecdd5f..bbdfe79e 100644 --- a/lib/clixon/clixon_yang_type.h +++ b/lib/clixon/clixon_yang_type.h @@ -55,11 +55,11 @@ * Prototypes */ int yang_type_cache_set(yang_type_cache **ycache, - yang_stmt *resolved, int options, cg_var *mincv, - cg_var *maxcv, char *pattern, uint8_t fraction); -int yang_type_cache_get(yang_type_cache *ycache, - yang_stmt **resolved, int *options, cg_var **mincv, - cg_var **maxcv, char **pattern, uint8_t *fraction); + yang_stmt *resolved, int options, + cvec *cvv, char *pattern, uint8_t fraction); +int yang_type_cache_get(yang_type_cache *ycache, yang_stmt **resolved, + int *options, cvec **cvv, char **pattern, + uint8_t *fraction); int yang_type_cache_cp(yang_type_cache **ycnew, yang_type_cache *ycold); int yang_type_cache_free(yang_type_cache *ycache); int ys_resolve_type(yang_stmt *ys, void *arg); @@ -69,12 +69,11 @@ yang_stmt *yang_find_identity(yang_stmt *ys, char *identity); int ys_cv_validate(cg_var *cv, yang_stmt *ys, char **reason); int clicon_type2cv(char *type, char *rtype, enum cv_type *cvtype); int yang_type_get(yang_stmt *ys, char **otype, yang_stmt **restype, - int *options, cg_var **mincv, cg_var **maxcv, char **pattern, + int *options, cvec **cvv, char **pattern, uint8_t *fraction_digits); -int yang_type_resolve(yang_stmt *ys, yang_stmt *ytype, - yang_stmt **restype, int *options, - cg_var **mincv, cg_var **maxcv, - char **pattern, uint8_t *fraction); +int yang_type_resolve(yang_stmt *ys, yang_stmt *ytype, + yang_stmt **restype, int *options, + cvec **cvv, char **pattern, uint8_t *fraction); #endif /* _CLIXON_YANG_TYPE_H_ */ diff --git a/lib/src/clixon_string.c b/lib/src/clixon_string.c index b3e5e588..733a1ca4 100644 --- a/lib/src/clixon_string.c +++ b/lib/src/clixon_string.c @@ -57,14 +57,15 @@ /*! Split string into a vector based on character delimiters. Using malloc * * The given string is split into a vector where the delimiter can be - * any of the characters in the specified delimiter string. + * _any_ of the characters in the specified delimiter string. * * The vector returned is one single memory block that must be freed * by the caller * * @code - * char **vec = NULL; - * int nvec; + * char **vec = NULL; + * char *v; + * int nvec; * if ((vec = clicon_strsep("/home/user/src/clixon", "/", &nvec)) == NULL) * err; * for (i=0; i [[a,"b"][c="d"] * kalle&c=d -> [[c="d"]] # Discard elements with no delim2 * XXX differentiate between error and null cvec. @@ -610,6 +614,26 @@ nodeid_split(char *nodeid, return retval; } +/*! Trim blanks from front and end of a string, return new string + * @param[in] str + * @retval s Pointer into existing str after trimming blanks + */ +char * +clixon_trim(char *str) +{ + char *s = str; + int i; + + while (strlen(s) && isblank(s[0])) + s++; + for (i=0; iys_argument, "enumeration")) goto done; diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 7a81bc6b..66590a56 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -1068,7 +1068,7 @@ ys_populate_leaf(yang_stmt *ys, yparent = ys->ys_parent; /* Find parent: list/container */ /* 1. Find type specification and set cv type accordingly */ - if (yang_type_get(ys, &type, &yrestype, &options, NULL, NULL, NULL, &fraction_digits) < 0) + if (yang_type_get(ys, &type, &yrestype, &options, NULL, NULL, &fraction_digits) < 0) goto done; restype = yrestype?yrestype->ys_argument:NULL; if (clicon_type2cv(type, restype, &cvtype) < 0) /* This handles non-resolved also */ @@ -1133,12 +1133,62 @@ ys_populate_list(yang_stmt *ys, return 0; } -/*! Populate range and length statements +/*! Set range or length boundary for built-in yang types + * Help functions to range and length statements + */ +static int +bound_add(yang_stmt *ys, + enum cv_type cvtype, + char *name, + char *val, + int options, + uint8_t fraction_digits + ) +{ + int retval = -1; + cg_var *cv; + char *reason = NULL; + int ret = 1; + + if ((cv = cvec_add(ys->ys_cvec, cvtype)) == NULL){ + clicon_err(OE_YANG, errno, "cvec_add"); + goto done; + } + if (cv_name_set(cv, name) == NULL){ + clicon_err(OE_YANG, errno, "cv_name_set(%s)", name); + goto done; + } + if (options & YANG_OPTIONS_FRACTION_DIGITS && cvtype == CGV_DEC64) + cv_dec64_n_set(cv, fraction_digits); + if (strcmp(val, "min") == 0) + cv_min_set(cv); + else if (strcmp(val, "max") == 0) + cv_max_set(cv); + else if ((ret = cv_parse1(val, cv, &reason)) < 0){ + clicon_err(OE_YANG, errno, "cv_parse1"); + goto done; + } + if (ret == 0){ /* parsing failed */ + clicon_err(OE_YANG, errno, "range statement %s: %s", val, reason); + free(reason); + goto done; + } + retval = 0; + done: + return retval; +} + +/*! Populate string built-in range statement * * Create cvec variables "range_min" and "range_max". Assume parent is type. - * Actually: min..max [| min..max]* - * where min,max is integer or keywords 'min' or 'max. - * We only allow one range, ie not 1..2|4..5 + * Actually: bound[..bound] (| bound[..bound])* + * where bound is integer, decimal or keywords 'min' or 'max. + * RFC 7950 9.2.4: + * A range consists of an explicit value, or a lower-inclusive bound, + * two consecutive dots "..", and an upper-inclusive bound. Multiple + * values or ranges can be given, separated by "|". If multiple values + * or ranges are given, they all MUST be disjoint and MUST be in + * ascending order */ static int ys_populate_range(yang_stmt *ys, @@ -1152,11 +1202,11 @@ ys_populate_range(yang_stmt *ys, int options = 0x0; uint8_t fraction_digits; enum cv_type cvtype = CGV_ERR; - char *minstr = NULL; - char *maxstr; - cg_var *cv; - char *reason = NULL; - int cvret; + char **vec = NULL; + char *v; + char *v2; + int nvec; + int i; yparent = ys->ys_parent; /* Find parent: type */ if (yparent->yn_keyword != Y_TYPE){ @@ -1164,81 +1214,90 @@ ys_populate_range(yang_stmt *ys, goto done; } if (yang_type_resolve(ys, (yang_stmt*)yparent, &yrestype, - &options, NULL, NULL, NULL, &fraction_digits) < 0) + &options, NULL, NULL, &fraction_digits) < 0) goto done; restype = yrestype?yrestype->ys_argument:NULL; origtype = yarg_id((yang_stmt*)yparent); /* This handles non-resolved also */ if (clicon_type2cv(origtype, restype, &cvtype) < 0) goto done; - /* special case for strings, where limit is length, not a string */ - if (cvtype == CGV_STRING) - cvtype = CGV_UINT64; - if ((minstr = strdup(ys->ys_argument)) == NULL){ - clicon_err(OE_YANG, errno, "strdup"); + if ((vec = clicon_strsep(ys->ys_argument, "|", &nvec)) == NULL) goto done; - } - if ((maxstr = strstr(minstr, "..")) != NULL){ - if (strlen(maxstr) < 2){ - clicon_err(OE_YANG, 0, "range statement: %s not on the form: ..", - ys->ys_argument); - goto done; - } - minstr[maxstr-minstr] = '\0'; - maxstr += 2; - /* minstr and maxstr need trimming */ - if (isblank(minstr[strlen(minstr)-1])) - minstr[strlen(minstr)-1] = '\0'; - if (isblank(maxstr[0])) - maxstr++; - if ((cv = cvec_add(ys->ys_cvec, cvtype)) == NULL){ - clicon_err(OE_YANG, errno, "cvec_add"); - goto done; + for (i=0; iys_cvec, cvtype)) == NULL){ - clicon_err(OE_YANG, errno, "cvec_add"); - goto done; - } - if (cv_name_set(cv, "range_max") == NULL){ - clicon_err(OE_YANG, errno, "cv_name_set"); - goto done; - } - if (options & YANG_OPTIONS_FRACTION_DIGITS && cvtype == CGV_DEC64) - cv_dec64_n_set(cv, fraction_digits); - if ((cvret = cv_parse1(maxstr, cv, &reason)) < 0){ - clicon_err(OE_YANG, errno, "cv_parse1"); - goto done; - } - if (cvret == 0){ /* parsing failed */ - clicon_err(OE_YANG, errno, "range statement, max: %s", reason); - free(reason); - goto done; - } + if (v2) + if (bound_add(ys, cvtype, "range_max",v2, + options, fraction_digits) < 0) + goto done; } retval = 0; done: - if (minstr) - free(minstr); + if (vec) + free(vec); + return retval; +} + +/*! Populate integer built-in length statement + * + * Create cvec variables "range_min" and "range_max". Assume parent is type. + * Actually: len[..len] (| len[..len])* + * len is unsigned integer or keywords 'min' or 'max. + * RFC 7950 9.4.4 + * A length range consists of an explicit value, or a lower bound, two + * consecutive dots "..", and an upper bound. Multiple values or ranges + * can be given, separated by "|". Length-restricting values MUST NOT + * be negative. If multiple values or ranges are given, they all MUST + * be disjoint and MUST be in ascending order. + */ +static int +ys_populate_length(yang_stmt *ys, + void *arg) +{ + int retval = -1; + yang_node *yparent; /* type */ + enum cv_type cvtype = CGV_ERR; + char **vec = NULL; + char *v; + int nvec; + int i; + char *v2; + + yparent = ys->ys_parent; /* Find parent: type */ + if (yparent->yn_keyword != Y_TYPE){ + clicon_err(OE_YANG, 0, "parent should be type"); + goto done; + } + cvtype = CGV_UINT64; + if ((vec = clicon_strsep(ys->ys_argument, "|", &nvec)) == NULL) + goto done; + for (i=0; iyc_resolved = resolved; ycache->yc_options = options; - if (mincv && (ycache->yc_mincv = cv_dup(mincv)) == NULL){ - clicon_err(OE_UNIX, errno, "cv_dup"); - goto done; - } - if (maxcv && (ycache->yc_maxcv = cv_dup(maxcv)) == NULL){ - clicon_err(OE_UNIX, errno, "cv_dup"); - goto done; + if (cvv){ + if ((ycache->yc_cvv = cvec_dup(cvv)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_dup"); + goto done; + } } if (pattern && (ycache->yc_pattern = strdup(pattern)) == NULL){ clicon_err(OE_UNIX, errno, "strdup"); @@ -155,8 +152,7 @@ int yang_type_cache_get(yang_type_cache *ycache, yang_stmt **resolved, int *options, - cg_var **mincv, - cg_var **maxcv, + cvec **cvv, char **pattern, uint8_t *fraction) { @@ -164,10 +160,8 @@ yang_type_cache_get(yang_type_cache *ycache, *resolved = ycache->yc_resolved; if (options) *options = ycache->yc_options; - if (mincv) - *mincv = ycache->yc_mincv; - if (maxcv) - *maxcv = ycache->yc_maxcv; + if (cvv) + *cvv = ycache->yc_cvv; if (pattern) *pattern = ycache->yc_pattern; if (fraction) @@ -181,14 +175,13 @@ yang_type_cache_cp(yang_type_cache **ycnew, { int retval = -1; int options; - cg_var *mincv; - cg_var *maxcv; + cvec *cvv; char *pattern; uint8_t fraction; yang_stmt *resolved; - yang_type_cache_get(ycold, &resolved, &options, &mincv, &maxcv, &pattern, &fraction); - if (yang_type_cache_set(ycnew, resolved, options, mincv, maxcv, pattern, fraction) < 0) + yang_type_cache_get(ycold, &resolved, &options, &cvv, &pattern, &fraction); + if (yang_type_cache_set(ycnew, resolved, options, cvv, pattern, fraction) < 0) goto done; retval = 0; done: @@ -198,10 +191,8 @@ yang_type_cache_cp(yang_type_cache **ycnew, int yang_type_cache_free(yang_type_cache *ycache) { - if (ycache->yc_mincv) - cv_free(ycache->yc_mincv); - if (ycache->yc_maxcv) - cv_free(ycache->yc_maxcv); + if (ycache->yc_cvv) + cvec_free(ycache->yc_cvv); if (ycache->yc_pattern) free(ycache->yc_pattern); free(ycache); @@ -220,8 +211,7 @@ ys_resolve_type(yang_stmt *ys, { int retval = -1; int options = 0x0; - cg_var *mincv = NULL; - cg_var *maxcv = NULL; + cvec *cvv = NULL; char *pattern = NULL; uint8_t fraction = 0; yang_stmt *resolved = NULL; @@ -231,12 +221,12 @@ ys_resolve_type(yang_stmt *ys, * Note that the resolved type could be ys itself. */ if (yang_type_resolve((yang_stmt*)ys->ys_parent, ys, &resolved, - &options, &mincv, &maxcv, &pattern, &fraction) < 0) + &options, &cvv, &pattern, &fraction) < 0) goto done; /* Cache the resolve locally */ if (yang_type_cache_set(&ys->ys_typecache, - resolved, options, mincv, maxcv, pattern, fraction) < 0) + resolved, options, cvv, pattern, fraction) < 0) goto done; retval = 0; done: @@ -388,132 +378,121 @@ static int cv_validate1(cg_var *cv, enum cv_type cvtype, int options, - cg_var *range_min, - cg_var *range_max, + cvec *cvv, char *pattern, yang_stmt *yrestype, char *restype, char **reason) { int retval = 1; /* OK */ + cg_var *cv1; + cg_var *cv2; int retval2; yang_stmt *yi = NULL; - unsigned int u = 0; - int i = 0; char *str; int found; char **vec = NULL; int nvec; char *v; + uint64_t uu = 0; + int64_t ii = 0; + int i; + int ret; if (reason && *reason){ free(*reason); *reason = NULL; } + /* check options first for length and range */ + if ((options & YANG_OPTIONS_RANGE) != 0 || + (options & YANG_OPTIONS_LENGTH) != 0){ + i = 0; + while (iys_argument:NULL; @@ -664,7 +623,7 @@ ys_cv_validate_union_one(yang_stmt *ys, } if (retval == 0) goto done; - if ((retval = cv_validate1(cvt, cvtype, options, range_min, range_max, + if ((retval = cv_validate1(cvt, cvtype, options, cvv, pattern, yrt, restype, reason)) < 0) goto done; } @@ -736,8 +695,7 @@ ys_cv_validate(cg_var *cv, int retval = -1; cg_var *ycv; /* cv of yang-statement */ int options = 0; - cg_var *range_min = NULL; - cg_var *range_max = NULL; + cvec *cvv = NULL; char *pattern = NULL; enum cv_type cvtype; char *type; /* orig type */ @@ -756,8 +714,7 @@ ys_cv_validate(cg_var *cv, } ycv = ys->ys_cv; if (yang_type_get(ys, &type, &yrestype, - &options, &range_min, &range_max, &pattern, - &fraction) < 0) + &options, &cvv, &pattern, &fraction) < 0) goto done; restype = yrestype?yrestype->ys_argument:NULL; if (clicon_type2cv(type, restype, &cvtype) < 0) @@ -782,7 +739,7 @@ ys_cv_validate(cg_var *cv, retval = retval2; /* invalid (0) with latest reason or valid 1 */ } else - if ((retval = cv_validate1(cv, cvtype, options, range_min, range_max, pattern, + if ((retval = cv_validate1(cv, cvtype, options, cvv, pattern, yrestype, restype, reason)) < 0) goto done; done: @@ -889,7 +846,17 @@ yang_find_identity(yang_stmt *ys, return yid; } -/* +/*! Resolve type restrictions, return contraining parameters + * @param[in] yrange Yang type range restriction if any + * @param[in] ylength Yang type length restriction if any + * @param[in] ypattern Yang type pattern restriction if any + * @param[in] yfraction Yang type fraction restriction if any + * @param[out] options Pointer to flags field of optional values. optional + * @param[out] cvv Pointer to cvec with min range or length. + * If options&YANG_OPTIONS_RANGE or YANG_OPTIONS_LENGTH + * @param[out] pattern Pointer to static string of yang string pattern. optional + * @param[out] fraction For decimal64, how many digits after period + * @retval 0 OK. */ static int resolve_restrictions(yang_stmt *yrange, @@ -897,19 +864,16 @@ resolve_restrictions(yang_stmt *yrange, yang_stmt *ypattern, yang_stmt *yfraction, int *options, - cg_var **mincv, - cg_var **maxcv, + cvec **cvv, char **pattern, uint8_t *fraction) { - if (options && mincv && maxcv && yrange != NULL){ - *mincv = cvec_find(yrange->ys_cvec, "range_min"); - *maxcv = cvec_find(yrange->ys_cvec, "range_max"); + if (options && cvv && yrange != NULL){ + *cvv = yrange->ys_cvec; *options |= YANG_OPTIONS_RANGE; } - if (options && mincv && maxcv && ylength != NULL){ - *mincv = cvec_find(ylength->ys_cvec, "range_min"); /* XXX fel typ */ - *maxcv = cvec_find(ylength->ys_cvec, "range_max"); + if (options && cvv && ylength != NULL){ + *cvv = ylength->ys_cvec; *options |= YANG_OPTIONS_LENGTH; } if (options && pattern && ypattern != NULL){ @@ -928,8 +892,8 @@ resolve_restrictions(yang_stmt *yrange, * @param[in] ytype yang-stmt object containing currently resolving type * @param[out] yrestype resolved type. return built-in type or NULL. mandatory * @param[out] options pointer to flags field of optional values. optional - * @param[out] mincv pointer to cv with min range or length. If options&YANG_OPTIONS_RANGE - * @param[out] maxcv pointer to cv with max range or length. If options&YANG_OPTIONS_RANGE + * @param[out] cvv pointer to cvec with min range or length. + * If options&YANG_OPTIONS_RANGE or YANG_OPTIONS_LENGTH * @param[out] pattern pointer to static string of yang string pattern. optional * @param[out] fraction for decimal64, how many digits after period * @retval 0 OK. Note yrestype may still be NULL. @@ -946,8 +910,7 @@ yang_type_resolve(yang_stmt *ys, yang_stmt *ytype, yang_stmt **yrestype, int *options, - cg_var **mincv, - cg_var **maxcv, + cvec **cvv, char **pattern, uint8_t *fraction) { @@ -970,8 +933,8 @@ yang_type_resolve(yang_stmt *ys, prefix = yarg_prefix(ytype); /* And this its prefix */ /* Cache does not work for eg string length 32? */ if (!yang_builtin(type) && ytype->ys_typecache != NULL){ - if (yang_type_cache_get(ytype->ys_typecache, - yrestype, options, mincv, maxcv, pattern, fraction) < 0) + if (yang_type_cache_get(ytype->ys_typecache, yrestype, + options, cvv, pattern, fraction) < 0) goto done; goto ok; } @@ -984,7 +947,7 @@ yang_type_resolve(yang_stmt *ys, if (prefix == NULL && yang_builtin(type)){ *yrestype = ytype; resolve_restrictions(yrange, ylength, ypattern, yfraction, options, - mincv, maxcv, pattern, fraction); + cvv, pattern, fraction); goto ok; } @@ -1022,13 +985,12 @@ yang_type_resolve(yang_stmt *ys, } /* recursively resolve this new type */ if (yang_type_resolve(ys, rytype, yrestype, - options, mincv, maxcv, pattern, fraction) < 0) + options, cvv, pattern, fraction) < 0) goto done; /* overwrites the resolved if any */ - resolve_restrictions(yrange, ylength, ypattern, yfraction, - options, mincv, maxcv, pattern, fraction); + resolve_restrictions(yrange, ylength, ypattern, yfraction, options, + cvv, pattern, fraction); } - ok: retval = 0; done: @@ -1042,11 +1004,11 @@ yang_type_resolve(yang_stmt *ys, * @code * yang_stmt *yrestype; * int options; - * int64_t min, max; + * cvec *cvv = NULL; * char *pattern; * uint8_t fraction; * - * if (yang_type_get(ys, &type, &yrestype, &options, &min, &max, &pattern, &fraction) < 0) + * if (yang_type_get(ys, &type, &yrestype, &options, &cvv, &pattern, &fraction) < 0) * goto err; * if (yrestype == NULL) # unresolved * goto err; @@ -1078,8 +1040,7 @@ yang_type_get(yang_stmt *ys, char **origtype, yang_stmt **yrestype, int *options, - cg_var **mincv, - cg_var **maxcv, + cvec **cvv, char **pattern, uint8_t *fraction ) @@ -1100,7 +1061,7 @@ yang_type_get(yang_stmt *ys, if (origtype) *origtype = type; if (yang_type_resolve(ys, ytype, yrestype, - options, mincv, maxcv, pattern, fraction) < 0) + options, cvv, pattern, fraction) < 0) goto done; clicon_debug(3, "%s: %s %s->%s", __FUNCTION__, ys->ys_argument, type, *yrestype?(*yrestype)->ys_argument:"null"); diff --git a/test/README.md b/test/README.md index 59832663..1326365e 100644 --- a/test/README.md +++ b/test/README.md @@ -2,8 +2,9 @@ This directory contains testing code for clixon and the example application. Assumes setup of http daemon as describe under apps/restonf -- clixon A top-level script clones clixon in /tmp and starts all.sh. You can copy this file (review it first) and place as cron script +- Jenkinsfile Makefile for Jenkins tests. Build clixon and run tests. - all.sh Run through all tests named 'test*.sh' in this directory. Therefore, if you place a test in this directory matching 'test*.sh' it will be run automatically. By default the script will exit on first error. Run as `all.sh summary` to continue and print a summary on all tests. +- site.sh Add your site-specific modifications here - test_nacm.sh Auth tests using internal NACM - test_nacm_ext.sh Auth tests using external NACM (separate file) - test_cli.sh CLI tests @@ -12,6 +13,7 @@ application. Assumes setup of http daemon as describe under apps/restonf - test_yang.sh Yang tests for constructs not in the example. - test_leafref.sh Yang leafref tests - test_datastore.sh Datastore tests +- and many more... Example runs: ``` diff --git a/test/test_openconfig.sh b/test/test_openconfig.sh index 47574fa2..f5f56bcf 100755 --- a/test/test_openconfig.sh +++ b/test/test_openconfig.sh @@ -3,9 +3,6 @@ # Note that the openconfig test suites are patched to counter CLixon issues as follows: # - release/models/mpls/openconfig-mpls-te.yang # issue: https://github.com/clicon/clixon/issues/60 -# - release/models/wifi/types/openconfig-wifi-types.yang -# issue: https://github.com/clicon/clixon/issues/59 -# # Yang specifics: multi-keys and empty type APPNAME=example diff --git a/test/test_type.sh b/test/test_type.sh index 20f6f7aa..188107a6 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -1,8 +1,10 @@ #!/bin/bash # Advanced union types and generated code # and enum w values +# XXX NO SUPPORT FOR lists of ranges and lengths !!! APPNAME=example # include err() and new() functions and creates $dir + . ./lib.sh cfg=$dir/conf_yang.xml @@ -123,57 +125,47 @@ module example{ enum down; } } - leaf length1 { - type string { - length "1"; - } - } -/* leaf length2 { - type string { - length "max"; - } - } - leaf length3 { - type string { - length "min"; - } - }*/ - leaf length4 { - type string { - length "4..4000"; - } - } -/* leaf length5 { - type string { - length "min..max"; - } - }*/ leaf num1 { type int32 { range "1"; } } -/* leaf num2 { - type int32 { - range "min"; - } - } - leaf num3 { - type int32 { - range "max"; - } - } -*/ - leaf num4 { + leaf num2 { type int32 { range "4..4000"; } } -/* leaf num5 { - type int32 { + leaf num3 { + type uint8 { range "min..max"; } - }*/ + } + leaf num4 { /* XXX multiple ranges not supported yet - only first*/ + type uint8 { + range "1 .. 2 | 42"; + } + } + leaf len1 { + type string { + length "2"; + } + } + leaf len2 { + type string { + length "4..4000"; + } + } + leaf len3 { + type string { + length "min..max"; + } + } + leaf len4 { + type string { + range "1 .. 2 | 42"; + } + } + typedef mybits { description "Test adding several bits"; type bits { @@ -303,6 +295,70 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '-1]]>]]>' "^]]>]]>$" + +new "netconf validate num1 -1 wrong" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationbad-elementnum1errorNumber out of range: -1]]>]]>$' + +new "cli range test num2 1000 ok" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set num2 1000" 0 "^$" + +new "cli range test num2 3 error" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set num2 3" 255 "^$" + +new "cli range test num2 5000 error" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set num2 5000" 255 "^$" + +new "cli range test num3 42 ok" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set num3 42" 0 "^$" + +new "cli range test num3 260 error" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set num3 260" 255 "^$" + +#----------------string ranges--------------------- + +new "cli length test len1 1 error" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set len1 x" 255 "^$" + +new "cli length test len1 2 OK" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set len1 xy" 0 "^$" + +new "cli length test len1 3 error" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set len1 hej" 255 "^$" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "netconf length set len1 1" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'x]]>]]>' "^]]>]]>$" + +new "netconf validate len1 1 wrong" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationbad-elementlen1errorstring length out of range: 1]]>]]>$' + +new "cli length test len2 42 ok" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set len2 hejhophdsakjhkjsadhkjsahdkjsad" 0 "^$" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "cli length test len2 3 error" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set len2 ab" 255 "^$" + +new "cli range test len3 42 ok" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set len3 hsakjdhkjsahdkjsahdksahdksajdhsakjhd" 0 "^$" + if [ $BE -eq 0 ]; then exit # BE fi From 207858e20dd8b068dd13e7d720aa7c663d87884b Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 10 Jan 2019 20:52:19 +0100 Subject: [PATCH 16/17] * Support of yangmodels supported, see test_yangmodels.sh * Added -o "