Merge branch 'master' into yang-patch-test

This commit is contained in:
alanyanigersiklu 2021-08-12 21:06:57 +03:00 committed by GitHub
commit 95a3664882
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
60 changed files with 1813 additions and 657 deletions

View file

@ -33,15 +33,37 @@
## 5.3.0
Expected: September, 2021
### C/CLI-API changes on existing features
### New features
Developers may need to change their code
* Restconf YANG PATCH according to RFC 8072
* Experimental: enable by setting YANG_PATCH in include/clixon_custom.h
* Thanks to Alan Yaniger for providing this patch
### API changes on existing protocol/config features
Users may have to change how they access the system
* Native Restconf is now default, not fcgi/nginx
* That is, to configure with fcgi, you need to explicitly configure: `--with-restconf=fcgi`
* New clixon-config@2021-07-11.yang revision
* Removed default of `CLICON_RESTCONF_INSTALLDIR`
* The default behaviour is changed to use the config $(sbindir) to locate `clixon_restconf` when starting restconf internally
### Minor features
* Added linenumbers to all YANG symbols for better debug and errors
* Improved error messages for YANG identityref:s and leafref:s by adding original line numbers
### Corrected Bugs
* Fixed: [clixon_netconf errors on client XML Declaration with valid encoding spec](https://github.com/clicon/clixon/issues/250)
* Fixed: Yang patterns: \n and other non-printable characters were broken
* Example: Clixon interpereted them two characters: `\\ n` instead of ascii 10
* Fixed: The auto-cli identityref did not expand identities in grouping/usecases properly.
* Fixed: [OpenConfig BGP afi-safi and when condition issues #249](https://github.com/clicon/clixon/issues/249)
* YANG when was not properly implemented for default values
* Fixed: SEGV in clixon_netconf_lib functions from internal errors including validation.
* Check xerr argument both before and after call on netconf lib functions
* Fixed: RFC 8040 yang-data extension allows non-key lists
* Added YANG_FLAG_NOKEY as exception to mandatory key lists
* Fixed: mandatory leaf in a uses statement caused abort

View file

@ -152,7 +152,9 @@ install-include: clixon_backend.h clixon_backend_handle.h clixon_backend_transac
.SUFFIXES: .c .o
.c.o:
$(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" $(CFLAGS) -c $<
# Note: CLIXON_CONFIG_SBINDIR is where clixon_restconf is believed to be installed, unless
# overruled by CLICON_RESTCONF_INSTALLDIR option
$(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" -DCLIXON_CONFIG_SBINDIR=\"$(sbindir)\" $(CFLAGS) -c $<
# Just link test programs
test.c :

View file

@ -238,7 +238,7 @@ client_get_streams(clicon_handle h,
cprintf(cb,"</%s>", top);
if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x, NULL) < 0){
if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0)
if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0)
goto done;
goto fail;
}

View file

@ -132,7 +132,7 @@ generic_validate(clicon_handle h,
cprintf(cb, "Mandatory variable of %s in module %s",
xml_parent(x1)?xml_name(xml_parent(x1)):"",
yang_argument_get(ys_module(ys)));
if (netconf_missing_element_xml(xret, "protocol", xml_name(x1), cbuf_get(cb)) < 0)
if (xret && netconf_missing_element_xml(xret, "protocol", xml_name(x1), cbuf_get(cb)) < 0)
goto done;
goto fail;
}
@ -480,9 +480,9 @@ startup_commit(clicon_handle h,
* and call application callback validations.
* @param[in] h Clicon handle
* @param[in] candidate The candidate database. The wanted backend state
* @param[out] xret Error XML tree. Free with xml_free after use
* @param[out] xret Error XML tree, if retval is 0. Free with xml_free after use
* @retval -1 Error - or validation failed (but cbret not set)
* @retval 0 Validation failed (with cbret set)
* @retval 0 Validation failed (with xret set)
* @retval 1 Validation OK
* @note Need to differentiate between error and validation fail
* (only done for generic_validate)
@ -505,16 +505,19 @@ validate_common(clicon_handle h,
goto done;
}
/* This is the state we are going to */
if (xmldb_get0(h, db, YB_MODULE, NULL, "/", 0, &td->td_target, NULL, NULL) < 0)
if ((ret = xmldb_get0(h, db, YB_MODULE, NULL, "/", 0, &td->td_target, NULL, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
/* Clear flags xpath for get */
xml_apply0(td->td_target, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
(void*)(XML_FLAG_MARK|XML_FLAG_CHANGE));
/* 2. Parse xml trees
* This is the state we are going from */
if (xmldb_get0(h, "running", YB_MODULE, NULL, "/", 0, &td->td_src, NULL, NULL) < 0)
if ((ret = xmldb_get0(h, "running", YB_MODULE, NULL, "/", 0, &td->td_src, NULL, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
/* Clear flags xpath for get */
xml_apply0(td->td_src, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
(void*)(XML_FLAG_MARK|XML_FLAG_CHANGE));
@ -606,11 +609,23 @@ candidate_validate(clicon_handle h,
if ((td = transaction_new()) == NULL)
goto done;
/* Common steps (with commit) */
if ((ret = validate_common(h, db, td, &xret)) < 1){
if ((ret = validate_common(h, db, td, &xret)) < 0){
/* A little complex due to several sources of validation fails or errors.
* (1) xerr is set -> translate to cbret; (2) cbret set use that; otherwise
* use clicon_err. */
if (xret && clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0)
* use clicon_err.
* TODO: -1 return should be fatal error, not failed validation
*/
if (!cbuf_len(cbret) &&
netconf_operation_failed(cbret, "application", clicon_err_reason)< 0)
goto done;
goto fail;
}
if (ret == 0){
if (xret == NULL){
clicon_err(OE_CFG, EINVAL, "xret is NULL");
goto done;
}
if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0)
goto done;
if (!cbuf_len(cbret) &&
netconf_operation_failed(cbret, "application", clicon_err_reason)< 0)

View file

@ -245,6 +245,7 @@ restconf_pseudo_process_control(clicon_handle h)
int i;
int nr;
cbuf *cb = NULL;
char *dir = NULL;
nr = 10;
if ((argv = calloc(nr, sizeof(char *))) == NULL){
@ -256,12 +257,18 @@ restconf_pseudo_process_control(clicon_handle h)
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* CLICON_RESTCONF_INSTALLDIR is where we think clixon_restconf is installed
* Problem is where to define it? Now in config file, but maybe it should be in configure?
* Tried Makefile but didnt work on Docker since it was moved around.
/* Try to figure out where clixon_restconf is installed
* If config option CLICON_RESTCONF_INSTALLDIR is installed, use that.
* If not, use the Makefile
* Use PATH?
*/
cprintf(cb, "%s/clixon_restconf", clicon_option_str(h, "CLICON_RESTCONF_INSTALLDIR"));
if ((dir = clicon_option_str(h, "CLICON_RESTCONF_INSTALLDIR")) == NULL){
if ((dir = CLIXON_CONFIG_SBINDIR) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "Both option CLICON_RESTCONF_INSTALLDIR and makefile constant CLIXON_CONFIG_SBINDIR are NULL which make sit not possible to know where clixon_restconf is installed(shouldnt happen)");
goto done;
}
}
cprintf(cb, "%s/clixon_restconf", dir);
argv[i++] = cbuf_get(cb);
argv[i++] = "-f";
argv[i++] = clicon_option_str(h, "CLICON_CONFIGFILE");

View file

@ -448,7 +448,7 @@ cli_debug_cli(clicon_handle h,
cg_var *cv;
int level;
if ((cv = cvec_find(vars, "level")) == NULL){
if ((cv = cvec_find_var(vars, "level")) == NULL){
if (cvec_len(argv) != 1){
clicon_err(OE_PLUGIN, EINVAL, "Requires either label var or single arg: 0|1");
goto done;
@ -479,7 +479,7 @@ cli_debug_backend(clicon_handle h,
cg_var *cv;
int level;
if ((cv = cvec_find(vars, "level")) == NULL){
if ((cv = cvec_find_var(vars, "level")) == NULL){
if (cvec_len(argv) != 1){
clicon_err(OE_PLUGIN, EINVAL, "Requires either label var or single arg: 0|1");
goto done;
@ -513,7 +513,7 @@ cli_debug_restconf(clicon_handle h,
cg_var *cv;
int level;
if ((cv = cvec_find(vars, "level")) == NULL){
if ((cv = cvec_find_var(vars, "level")) == NULL){
if (cvec_len(argv) != 1){
clicon_err(OE_PLUGIN, EINVAL, "Requires either label var or single arg: 0|1");
goto done;

View file

@ -184,7 +184,7 @@ yang2cli_helptext(cbuf *cb,
/*! Generate identityref statements for CLI variables
* @param[in] ys Yang statement
* @param[in] ytype Yang union type being resolved
* @param[in] ytype Resolved yang type.
* @param[in] helptext CLI help text
* @param[out] cb Buffer where cligen code is written
* @see yang2cli_var_sub Its sub-function
@ -208,8 +208,10 @@ yang2cli_var_identityref(yang_stmt *ys,
yang_stmt *yprefix;
yang_stmt *yspec;
if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) != NULL &&
(ybaseid = yang_find_identity(ys, yang_argument_get(ybaseref))) != NULL){
if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) == NULL)
goto ok;
if ((ybaseid = yang_find_identity(ytype, yang_argument_get(ybaseref))) == NULL)
goto ok;
idrefvec = yang_cvec_get(ybaseid);
if (cvec_len(idrefvec) > 0){
/* Add a wildchar string first -let validate take it for default prefix */
@ -243,7 +245,7 @@ yang2cli_var_identityref(yang_stmt *ys,
}
}
}
}
ok:
retval = 0;
done:
if (prefix)
@ -371,7 +373,7 @@ static int yang2cli_var_union(clicon_handle h, yang_stmt *ys, char *origtype,
* patterns, (eg regexp:"[0.9]*").
* @param[in] h Clixon handle
* @param[in] ys Yang statement
* @param[in] ytype Yang union type being resolved
* @param[in] ytype Resolved yang type.
* @param[in] helptext CLI help text
* @param[in] cvtype
* @param[in] options Flags field of optional values, see YANG_OPTIONS_*

View file

@ -178,11 +178,7 @@ netconf_get_config(clicon_handle h,
/* ie <filter>...</filter> */
if ((xfilter = xpath_first(xn, nsc, "%s%sfilter", prefix ? prefix : "", prefix ? ":" : "")) != NULL)
ftype = xml_find_value(xfilter, "type");
if (xfilter == NULL || ftype == NULL || strcmp(ftype, "xpath")==0){
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
}
else if (strcmp(ftype, "subtree")==0){
if (xfilter == NULL || ftype == NULL || strcmp(ftype, "subtree") == 0) {
/* Get whole config first, then filter. This is suboptimal
*/
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
@ -190,8 +186,11 @@ netconf_get_config(clicon_handle h,
/* Now filter on whole tree */
if (netconf_get_config_subtree(h, xfilter, xret) < 0)
goto done;
} else if (strcmp(ftype, "xpath") == 0) {
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) {
goto done;
}
else{
} else {
clixon_xml_parse_va(YB_NONE, NULL, xret, NULL, "<rpc-reply xmlns=\"%s\"><rpc-error>"
"<error-tag>operation-failed</error-tag>"
"<error-type>applicatio</error-type>"
@ -390,11 +389,7 @@ netconf_get(clicon_handle h,
/* ie <filter>...</filter> */
if ((xfilter = xpath_first(xn, nsc, "%s%sfilter", prefix ? prefix : "", prefix ? ":" : "")) != NULL)
ftype = xml_find_value(xfilter, "type");
if (xfilter == NULL || ftype == NULL || strcmp(ftype, "xpath")==0){
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
}
else if (strcmp(ftype, "subtree")==0){
if (xfilter == NULL || ftype == NULL || strcmp(ftype, "subtree") == 0) {
/* Get whole config + state first, then filter. This is suboptimal
*/
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
@ -402,8 +397,10 @@ netconf_get(clicon_handle h,
/* Now filter on whole tree */
if (netconf_get_config_subtree(h, xfilter, xret) < 0)
goto done;
}
else{
} else if (strcmp(ftype, "xpath") == 0) {
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
} else {
clixon_xml_parse_va(YB_NONE, NULL, xret, NULL, "<rpc-reply xmlns=\"%s\"><rpc-error>"
"<error-tag>operation-failed</error-tag>"
"<error-type>applicatio</error-type>"

View file

@ -529,6 +529,7 @@ restconf_path_root(evhtp_request_t *req,
clicon_err(OE_CFG, errno, "evbuffer_pullup");
goto done;
}
cbuf_reset(sd->sd_indata);
/* Note the pullup may not be null-terminated */
cbuf_append_buf(sd->sd_indata, buf, len);
}

View file

@ -419,6 +419,12 @@ main(int argc,
if (yang_spec_parse_module(h, "ietf-restconf", NULL, yspec)< 0)
goto done;
#ifdef YANG_PATCH
/* Load yang restconf patch module */
if (yang_spec_parse_module(h, "ietf-yang-patch", NULL, yspec)< 0)
goto done;
#endif // YANG_PATCH
/* Add netconf yang spec, used as internal protocol */
if (netconf_module_load(h) < 0)
goto done;

View file

@ -1737,6 +1737,12 @@ restconf_clixon_init(clicon_handle h,
if (yang_spec_parse_module(h, "ietf-restconf", NULL, yspec)< 0)
goto done;
#ifdef YANG_PATCH
/* Load yang restconf patch module */
if (yang_spec_parse_module(h, "ietf-yang-patch", NULL, yspec)< 0)
goto done;
#endif // YANG_PATCH
/* Add netconf yang spec, used as internal protocol */
if (netconf_module_load(h) < 0)
goto done;

View file

@ -65,6 +65,8 @@
/* cligen */
#include <cligen/cligen.h>
// TODO - remove this include if cbuf_trunc() is added to cligen repo
#include "../cligen/cligen_buf_internal.h"
/* clicon */
#include <clixon/clixon.h>
@ -74,6 +76,7 @@
#include "restconf_api.h"
#include "restconf_err.h"
#include "restconf_methods.h"
#include "restconf_methods_post.h"
/*! REST OPTIONS method
* According to restconf
@ -579,6 +582,710 @@ api_data_write(clicon_handle h,
return retval;
} /* api_data_write */
#ifdef YANG_PATCH
/*! Free memory after a NULL pointer check
*
* @param [in] str void pointer to memory to be freed
*
*/
static void yang_patch_free_mem(void *p)
{
if (p != NULL)
free(p);
}
/*! Return a value within XML tags
* @param [in] nsc namespace context
* @param [in] xn cxobj containing XML with the current edit
* @param [in] val cbuf to which the value will be written
* @param [in] key string containing the tag
* @retval 0 success
* @retval <0 failure
*/
static int yang_patch_get_xval(
cvec* nsc,
cxobj* xn,
cbuf* val,
const char* key
)
{
cxobj **vec = NULL;
size_t veclen = 0;
char* tmp_val = NULL;
int ret = xpath_vec(xn, nsc, "%s", &vec, &veclen, key);
if (ret < 0) {
return ret;
}
cxobj *xn_tmp = NULL;
if (veclen == 1) { //veclen should always be 1
xn_tmp = vec[0];
}
if (xn_tmp != NULL) {
tmp_val = xml_body(xn_tmp);
cbuf_append_str(val, tmp_val);
}
return 0;
}
// TODO - add this to cligen repo if it is approved
/*! Truncate a cbuf
*
* @param [in] cb cligen buffer allocated by cbuf_new(), may be reallocated.
* @param [in] int pos position at which to truncate
* @retval new cbuf containing the truncated string (old buffer remains as it was)
* @retval NULL Error
*/
cbuf*
cbuf_trunc(cbuf *cb,
int pos)
{
if (pos < 0 || pos > cb->cb_strlen){
errno = EINVAL;
return NULL;
}
/* Ensure buffer is right size */
cbuf* new_buf = cbuf_new_alloc(pos + 1);
if (new_buf == NULL)
return NULL;
strncpy(new_buf->cb_buffer, cb->cb_buffer, pos);
new_buf->cb_strlen = pos;
return new_buf;
}
/*! Add square brackets after the surrounding curly brackets in JSON
* Needed, in order to modify the result of xml2json_cbuf() to be valid input
* to api_dta_post() and api_dta_write()
* @param [in] x_simple_patch a cxobj to pass to xml2json_cbuf()
* @retval new cbuf with the modified json
* @retval NULL Error
*/
static cbuf* yang_patch_xml2json_modified_cbuf(cxobj* x_simple_patch)
{
cbuf *json_simple_patch = cbuf_new();
if (json_simple_patch == NULL)
return NULL;
cbuf* cb = cbuf_new();
xml2json_cbuf(cb, x_simple_patch, 1);
// Insert a '[' after the first '{' to get the JSON to match what api_data_post/write() expect
char *json_simple_patch_tmp = cbuf_get(cb);
int brace_count = 0;
for (int l = 0; l < strlen(json_simple_patch_tmp); l++) {
char c = json_simple_patch_tmp[l];
if (c == '{') {
brace_count++;
if (brace_count == 2) { // We've reached the second brace, insert a '[' before it
cbuf_append(json_simple_patch,(int)'[');
}
}
cbuf_append(json_simple_patch,(int)c);
}
cbuf* json_simple_patch_2 = NULL;
// Insert a ']' before the last '}' to get the JSON to match what api_data_post() expects
for (int l = cbuf_len(json_simple_patch) - 1; l >= 0; l--) {
char c = cbuf_get(json_simple_patch)[l];
if (c == '}') {
// Truncate and add a string, as there is not a function to insert a char into a cbuf
json_simple_patch_2 = cbuf_trunc(json_simple_patch, l);
cbuf_append_str(json_simple_patch_2, "]}");
break;
}
}
cbuf_free(json_simple_patch);
cbuf_free(cb);
return json_simple_patch_2;
}
/*!yang_patch_strip_after_last_slash
*
* Strip /... from end of val
* so that e.g. "/interface=eth2" becomes "/"
* or "/interface_list=mylist/interface=eth2" becomes "/interface_list=mylist/"
*
* @param[in] val value to strip
* @retval new cbuf with the stripped string
* @retval NULL error
*/
static cbuf* yang_patch_strip_after_last_slash(cbuf* val)
{
cbuf *cb = cbuf_new();
cbuf* val_tmp = cbuf_new();
cbuf_append_str(val_tmp, cbuf_get(val));
int idx = cbuf_len(val_tmp);
for (int l = cbuf_len(val_tmp) - 1; l>= 0; l--) {
if (cbuf_get(val_tmp)[l] == '/') {
idx = l;
break;
}
}
if (idx == cbuf_len(val_tmp)) // Didn't find a slash in the loop above
return NULL;
cbuf* val_tmp_2 = cbuf_trunc(val_tmp, idx + 1);
if (cbuf_append_str(cb, cbuf_get(val_tmp_2)) < 0)
return NULL;
cbuf_free(val_tmp);
cbuf_free(val_tmp_2);
return cb;
}
/*! YANG PATCH replace method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* @param[in] simplepatch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces"
* @param[in] target_val value in "target" field of edit in YANG patch
* @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch
* @param[in] value_vec pointer to the "value" array of an edit in YANG patch
* @param[in] x_simple_patch pointer to XML containing module name, e.g. <ietf-interfaces:interface/>
*/
static int yang_patch_do_replace (
clicon_handle h,
void *req,
int pi,
cvec *qvec,
int pretty,
restconf_media media_out,
ietf_ds_t ds,
cbuf* simple_patch_request_uri,
cbuf* target_val,
int value_vec_len,
cxobj** value_vec,
cxobj *x_simple_patch
)
{
cxobj * value_vec_tmp = NULL;
cbuf* delete_req_uri = cbuf_new();
if (delete_req_uri == NULL)
return 1;
// Make delete_req_uri something like "/restconf/data/ietf-interfaces:interfaces"
if (cbuf_append_str(delete_req_uri, cbuf_get(simple_patch_request_uri)) < 0)
return 1;
// Add the target to delete_req_uri,
// so it's something like "/restconf/data/ietf-interfaces:interfaces/interface=eth2"
if (cbuf_append_str(delete_req_uri, cbuf_get(target_val)) < 0)
return 1;
// Delete the object with the old values
int ret = api_data_delete(h, req, cbuf_get(delete_req_uri), pi, pretty, YANG_DATA_JSON, ds );
cbuf_free(delete_req_uri);
if (ret != 0)
return ret;
// Now set up for the post request.
// Strip /... from end of target val
// so that e.g. "/interface=eth2" becomes "/"
// or "/interface_list=mylist/interface=eth2" becomes "/interface_list=mylist/"
cbuf* post_req_uri = yang_patch_strip_after_last_slash(target_val);
// Make post_req_uri something like "/restconf/data/ietf-interfaces:interfaces"
if (cbuf_append_str(simple_patch_request_uri, cbuf_get(post_req_uri)))
return 1;
cbuf_free(post_req_uri);
// Now insert the new values into the data
// (which will include the key value and all other mandatory values)
for (int k = 0; k < value_vec_len; k++) {
if (value_vec[k] != NULL) {
value_vec_tmp = xml_dup(value_vec[k]);
xml_addsub(x_simple_patch, value_vec_tmp);
}
}
// Convert the data to json
cbuf *json_simple_patch = cbuf_new();
if (json_simple_patch == NULL)
return 1;
xml2json_cbuf(json_simple_patch, x_simple_patch, 1);
// Send the POST request
ret = api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, ds );
cbuf_free(json_simple_patch);
xml_free(value_vec_tmp);
return ret;
}
/*! YANG PATCH create method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* @param[in] simplepatch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces"
* @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch
* @param[in] value_vec pointer to the "value" array of an edit in YANG patch
* @param[in] x_simple_patch pointer to XML containing module name, e.g. <ietf-interfaces:interface/>
*/
static int yang_patch_do_create (
clicon_handle h,
void *req,
int pi,
cvec *qvec,
int pretty,
restconf_media media_out,
ietf_ds_t ds,
cbuf* simple_patch_request_uri,
int value_vec_len,
cxobj** value_vec,
cxobj *x_simple_patch
)
{
cxobj * value_vec_tmp = NULL;
for (int k = 0; k < value_vec_len; k++) {
if (value_vec[k] != NULL) {
value_vec_tmp = xml_dup(value_vec[k]);
xml_addsub(x_simple_patch, value_vec_tmp);
}
}
// Send the POST request
cbuf* cb = cbuf_new();
xml2json_cbuf(cb, x_simple_patch, 1);
char *json_simple_patch = cbuf_get(cb);
int ret = api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds );
xml_free(value_vec_tmp);
return ret;
}
/*! YANG PATCH insert method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] pi Offset, where to start pcvec
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* @param[in] simple_patch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces"
* @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch
* @param[in] value_vec pointer to the "value" array of an edit in YANG patch
* @param[in] x_simple_patch pointer to XML containing module name, e.g. <ietf-interfaces:interface/>
* @param[in] where_val value in "where" field of edit in YANG patch
* @param[in] api_path full API path, e.g. "/restconf/data/example-jukebox:jukebox/playlist=Foo-One"
* @param[in] point_val value in "point" field of edit in YANG patch
*/
static int yang_patch_do_insert (
clicon_handle h,
void *req,
int pi,
int pretty,
restconf_media media_out,
ietf_ds_t ds,
cbuf* simple_patch_request_uri,
int value_vec_len,
cxobj** value_vec,
cxobj *x_simple_patch,
cbuf* where_val,
char* api_path,
cbuf *point_val
)
{
cxobj * value_vec_tmp = NULL;
// Loop through the XML, and get each value
for (int k = 0; k < value_vec_len; k++) {
if (value_vec[k] != NULL) {
value_vec_tmp = xml_dup(value_vec[k]);
xml_addsub(x_simple_patch, value_vec_tmp);
}
}
cbuf *json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch);
if (json_simple_patch == NULL)
return 1;
// Set the insert attributes
cvec* qvec_tmp = NULL;
qvec_tmp = cvec_new(0);
if (qvec_tmp == NULL)
return 1;
cg_var *cv;
if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){
return 1;
}
cv_name_set(cv, "insert");
cv_string_set(cv, cbuf_get(where_val));
cbuf *point_str = cbuf_new();
if (point_str == NULL)
return 1;
cbuf_append_str(point_str, api_path);
cbuf_append_str(point_str, cbuf_get(point_val));
if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){
return 1;
}
cv_name_set(cv, "point");
cv_string_set(cv, cbuf_get(point_str));
// Send the POST request
int ret = api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec_tmp, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, ds );
xml_free(value_vec_tmp);
cbuf_free(point_str);
cbuf_free(json_simple_patch);
return ret;
}
/*! YANG PATCH merge method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* @param[in] simple_patch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces"
* @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch
* @param[in] value_vec pointer to the "value" array of an edit in YANG patch
* @param[in] x_simple_patch pointer to XML containing module name, e.g. "<ietf-interfaces:interface/>"
* @param[in] where_val value in "where" field of edit in YANG patch
* @param[in] key_xn XML with key tag and value, e.g. "<name>Foo-One</name>"
*/
static int yang_patch_do_merge (
clicon_handle h,
void *req,
cvec *pcvec,
int pi,
cvec *qvec,
int pretty,
restconf_media media_out,
ietf_ds_t ds,
cbuf* simple_patch_request_uri,
int value_vec_len,
cxobj** value_vec,
cxobj *x_simple_patch,
cxobj *key_xn
)
{
int ret = -1;
cxobj * value_vec_tmp = NULL;
if (key_xn != NULL)
xml_addsub(x_simple_patch, key_xn);
// Loop through the XML, create JSON from each one, and submit a simple patch
for (int k = 0; k < value_vec_len; k++) {
if (value_vec[k] != NULL) {
value_vec_tmp = xml_dup(value_vec[k]);
xml_addsub(x_simple_patch, value_vec_tmp);
}
cbuf* cb = cbuf_new();
xml2json_cbuf(cb, x_simple_patch, 1);
cbuf *json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch);
if (json_simple_patch == NULL)
return 1;
xml_free(value_vec_tmp);
// Send the simple patch request
ret = api_data_write(h, req, cbuf_get(simple_patch_request_uri), pcvec, pi, qvec, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, 1, ds );
cbuf_free(cb);
cbuf_free(json_simple_patch);
}
return ret;
}
/*! YANG PATCH method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] api_path0 According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* Netconf: <edit-config> (nc:operation="merge")
* See RFC8072
* YANG patch can be used to "create", "delete", "insert", "merge", "move", "replace", and/or
"remove" a resource within the target resource.
* Currently "move" not supported
*/
static int
api_data_yang_patch(clicon_handle h,
void *req,
char *api_path0,
cvec *pcvec,
int pi,
cvec *qvec,
char *data,
int pretty,
restconf_media media_out,
ietf_ds_t ds)
{
int retval = -1;
int i;
cxobj *xdata0 = NULL; /* Original -d data struct (including top symbol) */
cbuf *cbx = NULL;
cxobj *xtop = NULL; /* top of api-path */
cxobj *xbot = NULL; /* bottom of api-path */
yang_stmt *ybot = NULL; /* yang of xbot */
cxobj *xbot_tmp = NULL;
yang_stmt *yspec;
char *api_path;
cxobj *xret = NULL;
cxobj *xretcom = NULL; /* return from commit */
cxobj *xretdis = NULL; /* return from discard-changes */
cxobj *xerr = NULL; /* malloced must be freed */
int ret;
cvec *nsc = NULL;
yang_bind yb;
char *xpath = NULL;
cbuf *path_orig_1 = NULL;
clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path0);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
api_path=api_path0;
/* strip /... from start */
for (i=0; i<pi; i++)
api_path = index(api_path+1, '/');
/* Translate yang-patch path to xpath: xpath (cbpath) and namespace context (nsc) */
char yang_patch_path[] = "/ietf-yang-patch:yang-patch";
if ((ret = api_path2xpath(yang_patch_path, yspec, &xpath, &nsc, &xerr)) < 0)
goto done;
if (ret == 0){ /* validation failed */
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
/* Create config top-of-tree */
if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
goto done;
/* Translate yang-patch path to xml in the form of xtop/xbot */
xbot = xtop;
if ((ret = api_path2xml(yang_patch_path, yspec, xtop, YC_DATANODE, 1, &xbot, &ybot, &xerr)) < 0)
goto done;
if (ret == 0){ /* validation failed */
if (api_return_err(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
yb = YB_MODULE;
if ((ret = clixon_json_parse_string(data, yb, yspec, &xbot, &xerr)) < 0){
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if (ret == 0){
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
/*
* RFC 8072 2.1: The message-body MUST identify exactly one resource instance
*/
int nrchildren0 = 0;
cxobj *x = NULL;
if (xml_child_nr_type(xbot, CX_ELMNT) - nrchildren0 != 1){
if (netconf_malformed_message_xml(&xerr, "The message-body MUST contain exactly one instance of the expected data resource") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
size_t veclen;
cxobj **vec = NULL;
while ((x = xml_child_each(xbot, x, CX_ELMNT)) != NULL){
ret = xpath_vec(x, nsc, "edit", &vec, &veclen);
if (xml_flag(x, XML_FLAG_MARK)){
xml_flag_reset(x, XML_FLAG_MARK);
continue;
}
}
path_orig_1 = cbuf_new();
if (path_orig_1 == NULL) {
goto done;
} else {
cbuf_append_str(path_orig_1, restconf_uripath(h));
}
// Loop through the edits
for (int i = 0; i < veclen; i++) {
cxobj **tmp_vec = NULL;
size_t tmp_veclen = 0;
cxobj *xn = vec[i];
clicon_log_xml(LOG_DEBUG, xn, "%s %d xn:", __FUNCTION__, __LINE__);
// Get target
cbuf *target_val = cbuf_new();
ret = yang_patch_get_xval(nsc, xn, target_val, "target");
if (ret < 0) {
goto done;
}
// Get operation
cbuf *op_val = cbuf_new();
ret = yang_patch_get_xval(nsc, xn, op_val, "operation");
if (ret < 0) {
goto done;
}
// Get "point" and "where" for insert operations
cbuf *point_val = NULL;
cbuf *where_val = cbuf_new();
if (strcmp(cbuf_get(op_val), "insert") == 0) {
point_val = cbuf_new();
ret = yang_patch_get_xval(nsc, xn, point_val, "point");
if (ret < 0) {
goto done;
}
where_val = cbuf_new();
ret = yang_patch_get_xval(nsc, xn, where_val, "where");
if (ret < 0) {
goto done;
}
}
// Construct request URI
cbuf* simple_patch_request_uri = cbuf_new();
cbuf_append_str(simple_patch_request_uri, cbuf_get(path_orig_1));
cbuf* api_path_target = cbuf_new();
cbuf_append_str(api_path_target, api_path);
if (strcmp(cbuf_get(op_val), "merge") == 0) {
cbuf_append_str(api_path_target, cbuf_get(target_val));
cbuf_append_str(simple_patch_request_uri, cbuf_get(target_val));
}
if (xerr)
xml_free(xerr);
if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
goto done;
// Get key field
/* Translate api_path to xml in the form of xtop/xbot */
xbot_tmp = xtop;
if ((ret = api_path2xml(cbuf_get(api_path_target), yspec, xtop, YC_DATANODE, 1, &xbot_tmp, &ybot, &xerr)) < 0)
goto done;
if (ret == 0){ /* validation failed */
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
char *key_node_id = xml_name(xbot_tmp);
char *path = NULL;
if ((path = restconf_param_get(h, "REQUEST_URI")) != NULL){
for (int i1 = 0; i1 <pi; i1++)
path = index(path+1, '/');
}
const char colon[2] = ":";
char *modname = strtok(&(path[1]), colon);
cxobj **key_vec = NULL;
key_vec = xml_childvec_get(xbot_tmp);
cxobj *key_xn = NULL;
if (key_vec != NULL) {
key_xn = key_vec[0];
}
// Get values (for "delete" and "remove", there are no values)
xpath_vec(xn, nsc, "value", &tmp_vec, &tmp_veclen);
key_node_id = NULL;
// Loop through the values
for (int j = 0; j < tmp_veclen; j++) {
cxobj *values_xn = tmp_vec[j];
cxobj** values_child_vec = xml_childvec_get(values_xn);
if (key_node_id == NULL)
key_node_id = xml_name(*values_child_vec);
cbuf *patch_header = cbuf_new();
if (patch_header == NULL) {
goto done;
}
cbuf_append_str(patch_header, modname);
cbuf_append_str(patch_header, ":");
cbuf_append_str(patch_header, key_node_id);
cxobj *x_simple_patch = xml_new(cbuf_get(patch_header), NULL, CX_ELMNT);
if (x_simple_patch == NULL)
goto done;
int value_vec_len = xml_child_nr(*values_child_vec);
cxobj** value_vec = xml_childvec_get(*values_child_vec);
// For "replace", delete the item and then POST it
// TODO - in an ordered list, insert it into its original position
if (strcmp(cbuf_get(op_val),"replace") == 0) {
ret = yang_patch_do_replace(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, target_val, value_vec_len, value_vec, x_simple_patch);
if (ret != 0) {
goto done;
}
}
// For "create", put all the data values into a single POST request
if (strcmp(cbuf_get(op_val),"create") == 0) {
ret = yang_patch_do_create(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch);
if (ret != 0) {
goto done;
}
}
// For "insert", make a api_data_post request
if (strcmp(cbuf_get(op_val), "insert") == 0) {
ret = yang_patch_do_insert(h, req, pi, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch, where_val, api_path, point_val);
if (ret != 0) {
goto done;
}
}
// For merge", make single simple patch requests for each value
if (strcmp(cbuf_get(op_val),"merge") == 0) {
ret = yang_patch_do_merge(h, req, pcvec, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch, key_xn);
if (ret != 0) {
goto done;
}
}
cbuf_free(patch_header);
yang_patch_free_mem((void *)x_simple_patch); // Using xml_free() causes crash
}
if ((strcmp(cbuf_get(op_val), "delete") == 0) ||
(strcmp(cbuf_get(op_val), "remove") == 0)) {
cbuf_append_str(simple_patch_request_uri, cbuf_get(target_val));
if (strcmp(cbuf_get(op_val), "delete") == 0) {
// TODO - send error
} else {
// TODO - do not send error
}
api_data_delete(h, req, cbuf_get(simple_patch_request_uri), pi, pretty, YANG_DATA_JSON, ds);
}
cbuf_free(simple_patch_request_uri);
cbuf_free(api_path_target);
cbuf_free(target_val);
cbuf_free(op_val);
cbuf_free(point_val);
cbuf_free(where_val);
}
ok:
retval = 0;
done:
cbuf_free(path_orig_1);
yang_patch_free_mem((void *)vec);
yang_patch_free_mem((void *)xpath);
if (nsc)
xml_nsctx_free(nsc);
if (xret)
xml_free(xret);
if (xerr)
xml_free(xerr);
if (xretcom)
xml_free(xretcom);
if (xretdis)
xml_free(xretdis);
if (xtop)
xml_free(xtop);
if (xdata0)
xml_free(xdata0);
if (cbx)
cbuf_free(cbx);
return retval;
}
#endif // YANG_PATCH
/*! Generic REST PUT method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
@ -663,7 +1370,7 @@ api_data_patch(clicon_handle h,
ietf_ds_t ds)
{
restconf_media media_in;
int ret;
int ret = -1;
media_in = restconf_content_type(h);
switch (media_in){
@ -672,9 +1379,15 @@ api_data_patch(clicon_handle h,
ret = api_data_write(h, req, api_path0, pcvec, pi, qvec, data, pretty,
media_in, media_out, 1, ds);
break;
case YANG_PATCH_XML:
case YANG_PATCH_JSON: /* RFC 8072 patch */
case YANG_PATCH_XML:
#ifdef YANG_PATCH
ret = api_data_yang_patch(h, req, api_path0, pcvec, pi, qvec, data, pretty,
media_out, ds);
#else
ret = restconf_notimplemented(h, req, pretty, media_out);
#endif
break;
break;
default:
ret = restconf_unsupported_media(h, req, pretty, media_out);

View file

@ -39,6 +39,7 @@
#ifndef _RESTCONF_METHODS_H_
#define _RESTCONF_METHODS_H_
#define TEMP_STR_MALLOC_SIZE 5000
/*
* Prototypes
*/

View file

@ -155,6 +155,7 @@ api_data_post(clicon_handle h,
cvec *qvec,
char *data,
int pretty,
restconf_media media_in,
restconf_media media_out,
ietf_ds_t ds)
{
@ -178,7 +179,6 @@ api_data_post(clicon_handle h,
cxobj *x;
char *username;
int ret;
restconf_media media_in;
int nrchildren0 = 0;
yang_bind yb;
@ -231,7 +231,6 @@ api_data_post(clicon_handle h,
* If xbot is top-level (api_path=null) it does not have a spec therefore look for
* top-level (yspec) otherwise assume parent (xbot) is populated.
*/
media_in = restconf_content_type(h);
switch (media_in){
case YANG_DATA_XML:
if ((ret = clixon_xml_parse_string(data, yb, yspec, &xbot, &xerr)) < 0){

View file

@ -44,6 +44,7 @@
int api_data_post(clicon_handle h, void *req, char *api_path,
int pi, cvec *qvec, char *data,
int pretty,
restconf_media media_in,
restconf_media media_out, ietf_ds_t ds);
int api_operations_post(clicon_handle h, void *req, char *api_path,

View file

@ -331,7 +331,7 @@ api_data(clicon_handle h,
retval = api_data_get(h, req, api_path, pcvec, pi, qvec, pretty, media_out, ds);
}
else if (strcmp(request_method, "POST")==0) {
retval = api_data_post(h, req, api_path, pi, qvec, data, pretty, media_out, ds);
retval = api_data_post(h, req, api_path, pi, qvec, data, pretty, restconf_content_type(h), media_out, ds);
}
else if (strcmp(request_method, "PUT")==0) {
if (read_only)

View file

@ -101,6 +101,7 @@ BE_SRC = $(APPNAME)_backend.c
BE_OBJ = $(BE_SRC:%.c=%.o)
$(BE_PLUGIN): $(BE_OBJ)
ifeq ($(LINKAGE),static)
# can include -L in LDFLAGS?
$(CC) -Wall -shared $(LDFLAGS) -o $@ -lc $< -lclixon -L ../../apps/backend/ -lclixon_backend
else
$(CC) -Wall -shared $(LDFLAGS) -o $@ -lc $< -lclixon -lclixon_backend

View file

@ -23,5 +23,5 @@
<CLICON_NACM_MODE>disabled</CLICON_NACM_MODE>
<CLICON_STREAM_DISCOVERY_RFC5277>true</CLICON_STREAM_DISCOVERY_RFC5277>
<CLICON_MODULE_LIBRARY_RFC7895>false</CLICON_MODULE_LIBRARY_RFC7895>
<restconf><enable>true</enable><auth-type>none</auth-type><socket><namespace>default</namespace><address>0.0.0.0</address><port>8081</port><ssl>false</ssl></socket></restconf>
<restconf><enable>true</enable><auth-type>none</auth-type><socket><namespace>default</namespace><address>0.0.0.0</address><port>80</port><ssl>false</ssl></socket></restconf>
</clixon-config>

View file

@ -1012,6 +1012,7 @@ example_exit(clicon_handle h)
return 0;
}
/* Forward declaration */
clixon_plugin_api *clixon_plugin_init(clicon_handle h);
static clixon_plugin_api api = {

View file

@ -112,3 +112,8 @@
* solve all usecases, such as absolute usecases where the added node is looked for
*/
#define XML_PARENT_CANDIDATE
/*! Enable yang patch RFC 8072
* Remove this when regression test
*/
#undef YANG_PATCH

View file

@ -128,7 +128,6 @@ int netconf_operation_failed_xml(cxobj **xret, char *type, char *message);
int netconf_malformed_message(cbuf *cb, char *message);
int netconf_malformed_message_xml(cxobj **xret, char *message);
int netconf_data_not_unique_xml(cxobj **xret, cxobj *x, cvec *cvk);
int netconf_minmax_elements_xml(cxobj **xret, cxobj *xp, char *name, int max);
int netconf_trymerge(cxobj *x, yang_stmt *yspec, cxobj **xret);
int netconf_module_features(clicon_handle h);

View file

@ -100,6 +100,7 @@ int xml_chardata_encode(char **escp, const char *fmt, ...);
#endif
int xml_chardata_cbuf_append(cbuf *cb, char *str);
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 clicon_str2int_search(const map_str2int *mstab, char *str, int upper);

View file

@ -76,5 +76,6 @@ int assign_namespace_body(cxobj *x0, cxobj *x1);
int xml_merge(cxobj *x0, cxobj *x1, yang_stmt *yspec, char **reason);
int yang_enum_int_value(cxobj *node, int32_t *val);
int xml_copy_marked(cxobj *x0, cxobj *x1);
int yang_check_when_xpath(cxobj *xn, cxobj *xp, yang_stmt *yn, int *hit, int *nrp, char **xpathp);
#endif /* _CLIXON_XML_MAP_H_ */

View file

@ -212,6 +212,10 @@ char *yang_when_xpath_get(yang_stmt *ys);
int yang_when_xpath_set(yang_stmt *ys, char *xpath);
cvec *yang_when_nsc_get(yang_stmt *ys);
int yang_when_nsc_set(yang_stmt *ys, cvec *nsc);
const char *yang_filename_get(yang_stmt *ys);
int yang_filename_set(yang_stmt *ys, const char *filename);
int yang_linenum_get(yang_stmt *ys);
int yang_linenum_set(yang_stmt *ys, int linenum);
/* Other functions */
yang_stmt *yspec_new(void);

View file

@ -557,7 +557,7 @@ xmldb_readfile(clicon_handle h,
}
cprintf(cberr, "Internal error: %s", clicon_err_reason);
clicon_err_reset();
if (netconf_operation_failed_xml(xerr, "application", cbuf_get(cberr))< 0)
if (xerr && netconf_operation_failed_xml(xerr, "application", cbuf_get(cberr))< 0)
goto done;
cbuf_free(cberr);
goto fail;

View file

@ -1238,7 +1238,7 @@ _json_parse(char *str,
goto done;
}
cprintf(cberr, "Top-level JSON object %s is not qualified with namespace which is a MUST according to RFC 7951", xml_name(x));
if (netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0)
if (xerr && netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0)
goto done;
goto fail;
}

View file

@ -131,6 +131,10 @@ netconf_invalid_value_xml(cxobj **xret,
char *encstr = NULL;
cxobj *xa;
if (xret == NULL){
clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
goto done;
}
if (*xret == NULL){
if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
goto done;
@ -248,6 +252,10 @@ netconf_missing_attribute_xml(cxobj **xret,
char *encstr = NULL;
cxobj *xa;
if (xret == NULL){
clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
goto done;
}
if (*xret == NULL){
if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
goto done;
@ -356,6 +364,10 @@ netconf_bad_attribute_xml(cxobj **xret,
char *encstr = NULL;
cxobj *xa;
if (xret == NULL){
clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
goto done;
}
if (*xret == NULL){
if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
goto done;
@ -449,6 +461,10 @@ netconf_common_xml(cxobj **xret,
char *encstr = NULL;
cxobj *xa;
if (xret == NULL){
clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
goto done;
}
if (*xret == NULL){
if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
goto done;
@ -705,6 +721,10 @@ netconf_access_denied_xml(cxobj **xret,
char *encstr = NULL;
cxobj *xa;
if (xret == NULL){
clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
goto done;
}
if (*xret == NULL){
if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
goto done;
@ -941,6 +961,10 @@ netconf_data_missing_xml(cxobj **xret,
cxobj *xerr;
cxobj *xa;
if (xret == NULL){
clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
goto done;
}
if (*xret == NULL){
if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
goto done;
@ -1005,6 +1029,10 @@ netconf_operation_not_supported_xml(cxobj **xret,
char *encstr = NULL;
cxobj *xa;
if (xret == NULL){
clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
goto done;
}
if (*xret == NULL){
if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
goto done;
@ -1116,6 +1144,10 @@ netconf_operation_failed_xml(cxobj **xret,
char *encstr = NULL;
cxobj *xa;
if (xret == NULL){
clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
goto done;
}
if (*xret == NULL){
if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
goto done;
@ -1200,6 +1232,10 @@ netconf_malformed_message_xml(cxobj **xret,
char *encstr = NULL;
cxobj *xa;
if (xret == NULL){
clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
goto done;
}
if (*xret == NULL){
if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
goto done;
@ -1252,6 +1288,10 @@ netconf_data_not_unique_xml(cxobj **xret,
cbuf *cb = NULL;
cxobj *xa;
if (xret == NULL){
clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
goto done;
}
if (*xret == NULL){
if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
goto done;
@ -1316,6 +1356,10 @@ netconf_minmax_elements_xml(cxobj **xret,
cbuf *cb = NULL;
cxobj *xa;
if (xret == NULL){
clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
goto done;
}
if (*xret == NULL){
if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
goto done;
@ -1373,6 +1417,10 @@ netconf_trymerge(cxobj *x,
char *reason = NULL;
cxobj *xc;
if (xret == NULL){
clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
goto done;
}
if (*xret == NULL){
if ((*xret = xml_dup(x)) == NULL)
goto done;

View file

@ -89,6 +89,7 @@
* \p{Z} Separators [slp]?
* \p{S} Symbols [mcko]?
* \p{O} Other [cfon]?
* For non-printable, \n, \t, \r see https://www.regular-expressions.info/nonprint.html
*/
int
regexp_xsd2posix(char *xsd,
@ -124,6 +125,9 @@ regexp_xsd2posix(char *xsd,
case 'i': /* initial */
cprintf(cb, "[a-zA-Z_:]");
break;
case 'n': /* non-printable \n */
cprintf(cb, "\n");
break;
case 'p': /* category escape: \p{IsCategory} */
j = i+1;
if (j+2 < strlen(xsd) &&
@ -161,12 +165,18 @@ regexp_xsd2posix(char *xsd,
}
/* if syntax error, just leave it */
break;
case 'r': /* non-printable */
cprintf(cb, "\r");
break;
case 's':
cprintf(cb, "[ \t\r\n]");
break;
case 'S':
cprintf(cb, "[^ \t\r\n]");
break;
case 't': /* non-printable */
cprintf(cb, "\t");
break;
case 'w': /* word */
//cprintf(cb, "[0-9a-zA-Z_\\\\-]")
cprintf(cb, "[^[:punct:][:space:][:cntrl:]]");

View file

@ -114,11 +114,19 @@ validate_leafref(cxobj *xt,
cvec *nsc = NULL;
cbuf *cberr = NULL;
char *path;
yang_stmt *ymod;
if ((leafrefbody = xml_body(xt)) == NULL)
goto ok;
if ((ypath = yang_find(ytype, Y_PATH, NULL)) == NULL){
if (netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Leafref requires path statement") < 0)
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cberr, "Leafref requires path statement");
if (xret && netconf_missing_element_xml(xret, "application",
yang_argument_get(ytype),
cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
@ -140,8 +148,13 @@ validate_leafref(cxobj *xt,
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cberr, "Leafref validation failed: No leaf %s matching path %s", leafrefbody, path);
if (netconf_bad_element_xml(xret, "application", leafrefbody, cbuf_get(cberr)) < 0)
ymod = ys_module(ys);
cprintf(cberr, "Leafref validation failed: No leaf %s matching path %s in %s.yang:%d",
leafrefbody,
path,
yang_argument_get(ymod),
yang_linenum_get(ys));
if (xret && netconf_bad_element_xml(xret, "application", leafrefbody, cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
@ -211,7 +224,7 @@ validate_identityref(cxobj *xt,
/* Get idref value. Then see if this value is derived from ytype.
*/
if ((node = xml_body(xt)) == NULL){ /* It may not be empty */
if (netconf_bad_element_xml(xret, "application", xml_name(xt), "Identityref should not be empty") < 0)
if (xret && netconf_bad_element_xml(xret, "application", xml_name(xt), "Identityref should not be empty") < 0)
goto done;
goto fail;
}
@ -219,13 +232,13 @@ validate_identityref(cxobj *xt,
goto done;
/* This is the type's base reference */
if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) == NULL){
if (netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Identityref validation failed, no base") < 0)
if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Identityref validation failed, no base") < 0)
goto done;
goto fail;
}
/* This is the actual base identity */
if ((ybaseid = yang_find_identity(ybaseref, yang_argument_get(ybaseref))) == NULL){
if (netconf_missing_element_xml(xret, "application", yang_argument_get(ybaseref), "Identityref validation failed, no base identity") < 0)
if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(ybaseref), "Identityref validation failed, no base identity") < 0)
goto done;
goto fail;
}
@ -239,8 +252,11 @@ validate_identityref(cxobj *xt,
#endif
}
if (ymod == NULL){
cprintf(cberr, "Identityref validation failed, %s not derived from %s",
node, yang_argument_get(ybaseid));
cprintf(cberr, "Identityref validation failed, %s not derived from %s in %s.yang:%d",
node,
yang_argument_get(ybaseid),
yang_argument_get(ys_module(ybaseid)),
yang_linenum_get(ybaseid));
if (xret && netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
@ -252,9 +268,12 @@ validate_identityref(cxobj *xt,
*/
idrefvec = yang_cvec_get(ybaseid);
if (cvec_find(idrefvec, idref) == NULL){
cprintf(cberr, "Identityref validation failed, %s not derived from %s",
node, yang_argument_get(ybaseid));
if (netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0)
cprintf(cberr, "Identityref validation failed, %s not derived from %s in %s.yang:%d",
node,
yang_argument_get(ybaseid),
yang_argument_get(ys_module(ybaseid)),
yang_linenum_get(ybaseid));
if (xret && netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
@ -337,7 +356,7 @@ xml_yang_validate_rpc(clicon_handle h,
goto done;
/* Only accept resolved NETCONF base namespace */
if (namespace == NULL || strcmp(namespace, NETCONF_BASE_NAMESPACE) != 0){
if (netconf_unknown_namespace_xml(xret, "protocol", rpcprefix, "No appropriate namespace associated with prefix")< 0)
if (xret && netconf_unknown_namespace_xml(xret, "protocol", rpcprefix, "No appropriate namespace associated with prefix")< 0)
goto done;
goto fail;
}
@ -345,7 +364,7 @@ xml_yang_validate_rpc(clicon_handle h,
/* xn is name of rpc, ie <rcp><xn/></rpc> */
while ((xn = xml_child_each(xrpc, xn, CX_ELMNT)) != NULL) {
if ((yn = xml_spec(xn)) == NULL){
if (netconf_unknown_element_xml(xret, "application", xml_name(xn), NULL) < 0)
if (xret && netconf_unknown_element_xml(xret, "application", xml_name(xn), NULL) < 0)
goto done;
goto fail;
}
@ -434,7 +453,7 @@ check_choice(cxobj *xt,
continue; /* not choice */
break;
}
if (netconf_bad_element_xml(xret, "application", xml_name(x), "Element in choice statement already exists") < 0)
if (xret && netconf_bad_element_xml(xret, "application", xml_name(x), "Element in choice statement already exists") < 0)
goto done;
goto fail;
} /* while */
@ -487,7 +506,7 @@ check_list_key(cxobj *xt,
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
if (xml_find_type(xt, NULL, keyname, CX_ELMNT) == NULL){
if (netconf_missing_element_xml(xret, "application", keyname, "Mandatory key") < 0)
if (xret && netconf_missing_element_xml(xret, "application", keyname, "Mandatory key") < 0)
goto done;
goto fail;
}
@ -557,7 +576,7 @@ check_mandatory(cxobj *xt,
goto done;
}
cprintf(cb, "Mandatory variable of %s in module %s", xml_name(xt), yang_argument_get(ys_module(yc)));
if (netconf_missing_element_xml(xret, "application", yang_argument_get(yc), cbuf_get(cb)) < 0)
if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(yc), cbuf_get(cb)) < 0)
goto done;
goto fail;
}
@ -574,7 +593,7 @@ check_mandatory(cxobj *xt,
if (x == NULL){
/* @see RFC7950: 15.6 Error Message for Data That Violates
* a Mandatory "choice" Statement */
if (netconf_data_missing_xml(xret, yang_argument_get(yc), NULL) < 0)
if (xret && netconf_data_missing_xml(xret, yang_argument_get(yc), NULL) < 0)
goto done;
goto fail;
}
@ -707,7 +726,7 @@ check_unique_list(cxobj *x,
if (cvi==NULL){
/* Last element (i) is newly inserted, see if it is already there */
if (check_insert_duplicate(vec, i, vlen, sorted) < 0){
if (netconf_data_not_unique_xml(xret, x, cvk) < 0)
if (xret && netconf_data_not_unique_xml(xret, x, cvk) < 0)
goto done;
goto fail;
}
@ -751,7 +770,7 @@ check_min_max(cxobj *xp,
if ((ymin = yang_find(y, Y_MIN_ELEMENTS, NULL)) != NULL){
cv = yang_cv_get(ymin);
if (nr < cv_uint32_get(cv)){
if (netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 0) < 0)
if (xret && netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 0) < 0)
goto done;
goto fail;
}
@ -760,7 +779,7 @@ check_min_max(cxobj *xp,
cv = yang_cv_get(ymax);
if (cv_uint32_get(cv) > 0 && /* 0 means unbounded */
nr > cv_uint32_get(cv)){
if (netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 1) < 0)
if (xret && netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 1) < 0)
goto done;
goto fail;
}
@ -851,7 +870,7 @@ check_list_unique_minmax(cxobj *xt,
/* Only lists and leaf-lists are allowed to be many
* This checks duplicate container and leafs
*/
if (netconf_minmax_elements_xml(xret, xt, xml_name(x), 1) < 0)
if (xret && netconf_minmax_elements_xml(xret, xt, xml_name(x), 1) < 0)
goto done;
goto fail;
}
@ -1018,14 +1037,14 @@ xml_yang_validate_add(clicon_handle h,
* are considered as "" */
cvtype = cv_type_get(cv);
if (cv_isint(cvtype) || cvtype == CGV_BOOL || cvtype == CGV_DEC64){
if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), "Invalid NULL value") < 0)
if (xret && netconf_bad_element_xml(xret, "application", yang_argument_get(yt), "Invalid NULL value") < 0)
goto done;
goto fail;
}
}
else{
if (cv_parse1(body, cv, &reason) != 1){
if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0)
if (xret && netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0)
goto done;
goto fail;
}
@ -1033,7 +1052,7 @@ xml_yang_validate_add(clicon_handle h,
if ((ret = ys_cv_validate(h, cv, yt, NULL, &reason)) < 0)
goto done;
if (ret == 0){
if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0)
if (xret && netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0)
goto done;
goto fail;
}
@ -1136,6 +1155,7 @@ xml_yang_validate_all(clicon_handle h,
char *ns = NULL;
cbuf *cb = NULL;
cvec *nsc = NULL;
int hit = 0;
/* if not given by argument (overide) use default link
and !Node has a config sub-statement and it is false */
@ -1158,7 +1178,7 @@ xml_yang_validate_all(clicon_handle h,
goto done;
if (ns)
cprintf(cb, " in namespace: %s", ns);
if (netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0)
if (xret && netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0)
goto done;
goto fail;
}
@ -1217,7 +1237,7 @@ xml_yang_validate_all(clicon_handle h,
}
cprintf(cb, "Failed MUST xpath '%s' of '%s' in module %s",
xpath, xml_name(xt), yang_argument_get(ys_module(ys)));
if (netconf_operation_failed_xml(xret, "application",
if (xret && netconf_operation_failed_xml(xret, "application",
ye?yang_argument_get(ye):cbuf_get(cb)) < 0)
goto done;
goto fail;
@ -1227,55 +1247,23 @@ xml_yang_validate_all(clicon_handle h,
nsc = NULL;
}
}
/* First variant of when, actual "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */
if ((yc = yang_find(ys, Y_WHEN, NULL)) != NULL){
xpath = yang_argument_get(yc); /* "when" has xpath argument */
/* WHEN xpath needs namespace context */
if (xml_nsctx_yang(ys, &nsc) < 0)
if (yang_check_when_xpath(xt, xml_parent(xt), ys, &hit, &nr, &xpath) < 0)
goto done;
if ((nr = xpath_vec_bool(xt, nsc, "%s", xpath)) < 0)
goto done;
if (nsc){
xml_nsctx_free(nsc);
nsc = NULL;
}
if (nr == 0){
if (hit && nr == 0){
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "Failed WHEN condition of %s in module %s",
cprintf(cb, "Failed WHEN condition of %s in module %s (WHEN xpath is %s)",
xml_name(xt),
yang_argument_get(ys_module(ys)));
if (netconf_operation_failed_xml(xret, "application",
yang_argument_get(ys_module(ys)),
xpath);
if (xret && netconf_operation_failed_xml(xret, "application",
cbuf_get(cb)) < 0)
goto done;
goto fail;
}
}
/* Second variants of WHEN:
* Augmented and uses when using special info in node
*/
if ((xpath = yang_when_xpath_get(ys)) != NULL){
if ((nr = xpath_vec_bool(xml_parent(xt), yang_when_nsc_get(ys),
"%s", xpath)) < 0)
goto done;
if (nr == 0){
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "Failed augmented 'when' condition '%s' of node '%s' in module '%s'",
xpath,
xml_name(xt),
yang_argument_get(ys_module(ys)));
if (netconf_operation_failed_xml(xret, "application",
cbuf_get(cb)) < 0)
goto done;
goto fail;
}
}
}
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if ((ret = xml_yang_validate_all(h, x, xret)) < 0)

View file

@ -1142,7 +1142,9 @@ xml_default1(yang_stmt *yt,
int top = 0; /* Top symbol (set default namespace) */
int create = 0;
char *xpath;
int nr;
int nr = 0;
int hit = 0;
cg_var *cv;
if (xt == NULL){ /* No xml */
clicon_err(OE_XML, EINVAL, "No XML argument");
@ -1162,14 +1164,17 @@ xml_default1(yang_stmt *yt,
continue;
switch (yang_keyword_get(yc)){
case Y_LEAF:
if (!cv_flag(yang_cv_get(yc), V_UNSET)){ /* Default value exists */
/* Check when statement from uses or augment */
if ((xpath = yang_when_xpath_get(yc)) != NULL){
if ((nr = xpath_vec_bool(xt, yang_when_nsc_get(yc), "%s", xpath)) < 0)
if ((cv = yang_cv_get(yc)) == NULL){
clicon_err(OE_YANG,0, "Internal error: yang leaf %s not populated with cv as it should",
yang_argument_get(yc));
goto done;
if (nr == 0)
break; /* Do not create default if xpath fails */
}
if (!cv_flag(cv, V_UNSET)){ /* Default value exists */
/* Check when condition */
if (yang_check_when_xpath(NULL, xt, yc, &hit, &nr, &xpath) < 0)
goto done;
if (hit && nr == 0)
break; /* Do not create default if xpath fails */
if (xml_find_type(xt, NULL, yang_argument_get(yc), CX_ELMNT) == NULL){
/* No such child exist, create this leaf */
if (xml_default_create(yc, xt, top) < 0)
@ -1180,13 +1185,11 @@ xml_default1(yang_stmt *yt,
break;
case Y_CONTAINER:
if (yang_find(yc, Y_PRESENCE, NULL) == NULL){
/* Check when statement from uses or augment */
if ((xpath = yang_when_xpath_get(yc)) != NULL){
if ((nr = xpath_vec_bool(xt, yang_when_nsc_get(yc), "%s", xpath)) < 0)
/* Check when condition */
if (yang_check_when_xpath(NULL, xt, yc, &hit, &nr, &xpath) < 0)
goto done;
if (nr == 0)
if (hit && nr == 0)
break; /* Do not create default if xpath fails */
}
/* If this is non-presence, (and it does not exist in xt) call
* recursively and create nodes if any default value exist first.
* Then continue and populate?
@ -2257,3 +2260,70 @@ xml_copy_marked(cxobj *x0,
return retval;
}
/*! Check when condition
*
* @param[in] h Clixon handle
* @param[in] xn XML node, can be NULL, in which case it is added as dummy under xp
* @param[in] xp XML parent
* @param[in] ys Yang node
* First variants of WHEN: Augmented and uses when using special info in node
* Second variant of when, actual "when" sub-node RFC 7950 Sec 7.21.5. Can only be one.
*/
int
yang_check_when_xpath(cxobj *xn,
cxobj *xp,
yang_stmt *yn,
int *hit,
int *nrp,
char **xpathp)
{
int retval = 1;
yang_stmt *yc;
char *xpath = NULL;
cxobj *x = NULL;
int nr = 0;
cvec *nsc = NULL;
int xmalloc = 0; /* ugly help variable to clean temporary object */
int nscmalloc = 0; /* ugly help variable to remove */
/* First variant */
if ((xpath = yang_when_xpath_get(yn)) != NULL){
x = xp;
nsc = yang_when_nsc_get(yn);
*hit = 1;
}
/* Second variant */
else if ((yc = yang_find(yn, Y_WHEN, NULL)) != NULL){
xpath = yang_argument_get(yc); /* "when" has xpath argument */
/* Create dummy */
if (xn == NULL){
if ((x = xml_new(yang_argument_get(yn), xp, CX_ELMNT)) == NULL)
goto done;
xml_spec_set(x, yn);
xmalloc++;
}
else
x = xn;
if (xml_nsctx_yang(yn, &nsc) < 0)
goto done;
nscmalloc++;
*hit = 1;
}
else
*hit = 0;
if (x && xpath){
if ((nr = xpath_vec_bool(x, nsc, "%s", xpath)) < 0)
goto done;
}
if (nrp)
*nrp = nr;
if (xpathp)
*xpathp = xpath;
retval = 0;
done:
if (xmalloc)
xml_purge(x);
if (nsc && nscmalloc)
xml_nsctx_free(nsc);
return retval;
}

View file

@ -65,6 +65,7 @@
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <stdlib.h>
@ -196,7 +197,7 @@ static int
xml_parse_encoding(clixon_xml_yacc *xy,
char *enc)
{
if(strcmp(enc, "UTF-8")){
if(strcasecmp(enc, "UTF-8")){
clicon_err(OE_XML, XMLPARSE_ERRNO, "Unsupported XML encoding: %s expected UTF-8", enc);
free(enc);
return -1;

View file

@ -290,8 +290,11 @@ xpath_tree2cbuf(xpath_tree *xs,
switch (xs->xs_type){
case XP_AND: /* and or */
case XP_ADD: /* div mod + * - */
if (xs->xs_c1)
cprintf(xcb, " %s ", clicon_int2str(xpopmap, xs->xs_int));
break;
case XP_RELEX: /* !=, >= <= < > = */
case XP_UNION:
case XP_UNION: /* | */
if (xs->xs_c1)
cprintf(xcb, "%s", clicon_int2str(xpopmap, xs->xs_int));
break;

View file

@ -340,6 +340,8 @@ yang_flag_reset(yang_stmt *ys,
* @param[in] ys Yang statement
* @retval xpath xpath should evaluate to true at validation
* @retval NULL Not set
* Note xpath context is PARENT which is different from when actual when child which is
* child itself
*/
char*
yang_when_xpath_get(yang_stmt *ys)
@ -414,6 +416,61 @@ yang_when_nsc_set(yang_stmt *ys,
return retval;
}
/*! Get yang filename for error/debug purpose
*
* @param[in] ys Yang statement
* @retval filename
* @note there maye not always be a "filename" in case the yang is read from memory
*/
const char *
yang_filename_get(yang_stmt *ys)
{
return ys->ys_filename;
}
/*! Set yang filename for error/debug purpose
*
* @param[in] ys Yang statement
* @param[in] filename
* @retval 0 OK
* @retval -1 Error
* @note there maye not always be a "filename" in case the yang is read from memory
*/
int
yang_filename_set(yang_stmt *ys,
const char *filename)
{
if ((ys->ys_filename = strdup(filename)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
return -1;
}
return 0;
}
/*! Get line number of yang filename for error/debug purpose
*
* @param[in] ys Yang statement
* @retval linenum
*/
int
yang_linenum_get(yang_stmt *ys)
{
return ys->ys_linenum;
}
/*! Set line number of yang filename for error/debug purpose
*
* @param[in] ys Yang statement
* @param[in] linenum
*/
int
yang_linenum_set(yang_stmt *ys,
int linenum)
{
ys->ys_linenum = linenum;
return 0;
}
/* End access functions */
/*! Create new yang specification
@ -496,6 +553,8 @@ ys_free1(yang_stmt *ys,
cvec_free(ys->ys_when_nsc);
if (ys->ys_stmt)
free(ys->ys_stmt);
if (ys->ys_filename)
free(ys->ys_filename);
if (self)
free(ys);
return 0;

View file

@ -93,7 +93,8 @@ struct yang_stmt{
char *ys_when_xpath; /* Special conditional for a "when"-associated augment/uses xpath */
cvec *ys_when_nsc; /* Special conditional for a "when"-associated augment/uses namespace ctx */
int _ys_vector_i; /* internal use: yn_each */
char *ys_filename; /* For debug/errors: filename (only (sub)modules) */
int ys_linenum; /* For debug/errors: line number (in ys_filename) */
};

View file

@ -112,6 +112,7 @@ modstate_diff_free(modstate_diff_t *md)
*
* Load RFC7895 yang spec, module-set-id, etc.
* @param[in] h Clicon handle
* @see netconf_module_load
*/
int
yang_modules_init(clicon_handle h)
@ -328,7 +329,7 @@ yang_modules_state_get(clicon_handle h,
* Note, list is not sorted since it is state (should not be)
*/
if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x, NULL) < 0){
if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0)
if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0)
goto done;
goto fail;
}

View file

@ -318,6 +318,7 @@ ysp_add(clixon_yang_yacc *yy,
goto err;
if (ys_parse_sub(ys, extra) < 0) /* Check statement-specific syntax */
goto err2; /* dont free since part of tree */
yang_linenum_set(ys, yy->yy_linenum); /* For error/debugging */
// done:
return ys;
err:

View file

@ -773,6 +773,9 @@ yang_parse_str(char *str,
goto done;
}
ymod = yy.yy_module;
/* Add filename for debugging and errors, see also ys_linenum on (each symbol?) */
if (yang_filename_set(ymod, name) < 0)
goto done;
done:
ystack_pop(&yy);
if (yy.yy_stack)

View file

@ -1,6 +1,7 @@
#!/usr/bin/env bash
# Identity and identityref tests
# Example from RFC7950 Sec 7.18 and 9.10
# Extended with a submodule
# Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
@ -60,7 +61,6 @@ cat <<EOF > $dir/example-crypto-base.yang
algorithms.";
}
}
EOF
cat <<EOF > $dir/example-des.yang
@ -85,10 +85,11 @@ cat <<EOF > $dir/example-des.yang
EOF
cat <<EOF > $fyang
module example {
module example-my-crypto {
yang-version 1.1;
namespace "urn:example:my-crypto";
prefix mc;
include "example-sub";
import "example-crypto-base" {
prefix "crypto";
}
@ -141,6 +142,45 @@ cat <<EOF > $fyang
base mc:empty;
}
}
uses myname;
}
EOF
# Only included from sub-module
# Introduce an identity only visible by example-sub submodule
cat <<EOF > $dir/example-extra.yang
module example-extra {
yang-version 1.1;
namespace "urn:example:extra";
prefix ee;
identity extra-base;
identity extra-new{
base ee:extra-base;
}
identity extra-old{
base ee:extra-base;
}
}
EOF
# Sub-module
cat <<EOF > $dir/example-sub.yang
submodule example-sub {
yang-version 1.1;
belongs-to example-my-crypto {
prefix mc;
}
import example-extra {
prefix ee;
}
grouping myname {
leaf sub-name {
description "Uses identity accessed by only the submodule";
type identityref {
base ee:extra-base;
}
}
}
}
EOF
@ -212,7 +252,7 @@ new "Set crypto to foo:bar"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><crypto xmlns=\"urn:example:my-crypto\">foo:bar</crypto></config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "netconf validate (expect fail)"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>Identityref validation failed, foo:bar not derived from crypto-alg</error-message></rpc-error></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>Identityref validation failed, foo:bar not derived from crypto-alg in example-crypto-base.yang:[0-9]*</error-message></rpc-error></rpc-reply>]]>]]>$"
new "cli set crypto to mc:aes"
expectpart "$($clixon_cli -1 -f $cfg -l o set crypto mc:aes)" 0 "^$"
@ -242,7 +282,7 @@ new "Netconf set undefined acl-type"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><acls xmlns=\"urn:example:my-crypto\"><acl><name>x</name><type>undefined</type></acl></acls></config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "netconf validate fail"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>Identityref validation failed, undefined not derived from acl-base</error-message></rpc-error></rpc-reply>]]>]]>"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>Identityref validation failed, undefined not derived from acl-base in example-my-crypto.yang:[0-9]*</error-message></rpc-error></rpc-reply>]]>]]>"
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><discard-changes/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
@ -269,42 +309,61 @@ expectpart "$($clixon_cli -1 -f $cfg -l o validate)" 255 "Validate failed. Edit
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><discard-changes/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
# Special case sub-module
new "auto-cli cli expansion submodule identity"
expectpart "$(echo "set sub-name ?" | $clixon_cli -f $cfg 2>&1)" 0 "set sub-name" "ee:extra-new" "ee:extra-old"
new "cli add identity"
expectpart "$($clixon_cli -1 -f $cfg -l o set sub-name ee:extra-new)" 0 ""
new "cli validate submodule identity"
expectpart "$($clixon_cli -1 -f $cfg -l o validate)" 0 ""
new "cli add wrong identity"
expectpart "$($clixon_cli -1 -f $cfg -l o set sub-name ee:foo)" 0 ""
new "cli validate wrong id (expect fail)"
expectpart "$($clixon_cli -1 -f $cfg -l o validate 2>&1)" 255 "Identityref validation failed, ee:foo not derived from extra-base"
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><discard-changes/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
# restconf and identities:
# 1. set identity in own module with restconf (PUT and POST), read it with restconf and netconf
# 2. set identity in other module with restconf , read it with restconf and netconf
# 3. set identity in other module with netconf, read it with restconf and netconf
new "restconf add own identity"
expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:crypto -d '{"example:crypto":"example:aes"}')" 0 "HTTP/$HVER 201"
expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example-my-crypto:crypto -d '{"example-my-crypto:crypto":"example-my-crypto:aes"}')" 0 "HTTP/$HVER 201"
new "restconf get own identity"
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 200" '{"example:crypto":"aes"}'
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-my-crypto:crypto)" 0 "HTTP/$HVER 200" '{"example-my-crypto:crypto":"aes"}'
new "netconf get own identity as set by restconf"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-config><source><running/></source></get-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><data><crypto xmlns=\"urn:example:my-crypto\">aes</crypto>"
new "restconf delete identity"
expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 204"
expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example-my-crypto:crypto)" 0 "HTTP/$HVER 204"
# 2. set identity in other module with restconf , read it with restconf and netconf
if ! $YANG_UNKNOWN_ANYDATA ; then
new "restconf add POST instead of PUT (should fail)"
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:crypto -d '{"example:crypto":"example-des:des3"}')" 0 "HTTP/$HVER 400" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: crypto with parent: crypto in namespace: urn:example:my-crypto"}}}'
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example-my-crypto:crypto -d '{"example-my-crypto:crypto":"example-des:des3"}')" 0 "HTTP/$HVER 400" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: crypto with parent: crypto in namespace: urn:example:my-crypto"}}}'
fi
# Alternative error:
#'{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Leaf contains sub-element"}}}'
new "restconf add other (des) identity using POST"
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"example:crypto":"example-des:des3"}')" 0 "HTTP/$HVER 201" "Location: $RCPROTO://localhost/restconf/data/example:crypto"
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"example-my-crypto:crypto":"example-des:des3"}')" 0 "HTTP/$HVER 201" "Location: $RCPROTO://localhost/restconf/data/example-my-crypto:crypto"
new "restconf get other identity"
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 200" '{"example:crypto":"example-des:des3"}'
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-my-crypto:crypto)" 0 "HTTP/$HVER 200" '{"example-my-crypto:crypto":"example-des:des3"}'
new "netconf get other identity"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-config><source><running/></source></get-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><data><crypto xmlns=\"urn:example:my-crypto\" xmlns:des=\"urn:example:des\">des:des3</crypto>"
new "restconf delete identity"
expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 204"
expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example-my-crypto:crypto)" 0 "HTTP/$HVER 204"
# 3. set identity in other module with netconf, read it with restconf and netconf
new "netconf set other identity"
@ -314,7 +373,7 @@ new "netconf commit"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><commit/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "restconf get other identity (set by netconf)"
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 200" '{"example:crypto":"example-des:des3"}'
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-my-crypto:crypto)" 0 "HTTP/$HVER 200" '{"example-my-crypto:crypto":"example-des:des3"}'
new "netconf get other identity"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-config><source><running/></source></get-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><data><crypto xmlns=\"urn:example:my-crypto\" xmlns:des=\"urn:example:des\">des:des3</crypto>"

View file

@ -67,6 +67,19 @@ module example{
default 31; /* should be set on startup */
}
}
/* Extra rules to check when condition */
leaf npleaf{
when "../s3 = '99'";
type uint32;
default 98;
}
container npcont{
when "../s3 = '99'";
leaf npext{
type uint32;
default 99;
}
}
}
container p4{
presence "A presence container";
@ -144,6 +157,13 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-confi
new "get config (should contain y/inside+outside)"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><data>$XML<xs-config xmlns=\"urn:example:clixon\"><x><name>a</name><y><inside>false</inside></y><outside>false</outside></x></xs-config></data></rpc-reply>]]>]]>$"
# Set s3 leaf to 99 triggering when condition for default values
new "Set s3 to 99"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><np3 xmlns=\"urn:example:clixon\"><s3>99</s3></np3></config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "get config np3 with npleaf and npext"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/ex:np3\" xmlns:ex=\"urn:example:clixon\" /></get-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><data><np3 xmlns=\"urn:example:clixon\"><s3>99</s3><np31><s31>31</s31></np31><npleaf>98</npleaf><npcont><npext>99</npext></npcont></np3></data></rpc-reply>]]>]]>$"
if [ $BE -ne 0 ]; then
new "Kill backend"
# Check if premature kill

View file

@ -140,7 +140,7 @@ new "leafref add non-existing ref"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><default-address xmlns=\"urn:example:clixon\"><absname>eth3</absname><address>10.0.4.6</address></default-address></config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "leafref validate"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>bad-element</error-tag><error-info><bad-element>eth3</bad-element></error-info><error-severity>error</error-severity><error-message>Leafref validation failed: No leaf eth3 matching path /if:interfaces/if:interface/if:name</error-message></rpc-error></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>bad-element</error-tag><error-info><bad-element>eth3</bad-element></error-info><error-severity>error</error-severity><error-message>Leafref validation failed: No leaf eth3 matching path /if:interfaces/if:interface/if:name in example.yang:[0-9]*</error-message></rpc-error></rpc-reply>]]>]]>$"
#new "leafref wrong ref"
#expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><default-address xmlns=\"urn:example:clixon\"><wrong>eth3</wrong><address>10.0.4.6</address></default-address></config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
@ -170,7 +170,7 @@ new "leafref delete leaf"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\" xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><interface nc:operation=\"delete\"><name>eth0</name></interface></interfaces></config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "leafref validate (should fail)"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>bad-element</error-tag><error-info><bad-element>eth0</bad-element></error-info><error-severity>error</error-severity><error-message>Leafref validation failed: No leaf eth0 matching path /if:interfaces/if:interface/if:name</error-message></rpc-error></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>bad-element</error-tag><error-info><bad-element>eth0</bad-element></error-info><error-severity>error</error-severity><error-message>Leafref validation failed: No leaf eth0 matching path /if:interfaces/if:interface/if:name in example.yang:[0-9]*</error-message></rpc-error></rpc-reply>]]>]]>$"
new "leafref discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><discard-changes/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"

View file

@ -209,7 +209,7 @@ new "leafref augment+leafref config wrong ref"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config>$XML</config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "leafref augment+leafref validate wrong ref"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>bad-element</error-tag><error-info><bad-element>xxx</bad-element></error-info><error-severity>error</error-severity><error-message>Leafref validation failed: No leaf xxx matching path /ex:sender/ex:name</error-message></rpc-error></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>bad-element</error-tag><error-info><bad-element>xxx</bad-element></error-info><error-severity>error</error-severity><error-message>Leafref validation failed: No leaf xxx matching path /ex:sender/ex:name in augment.yang:[0-9]*</error-message></rpc-error></rpc-reply>]]>]]>$"
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><discard-changes/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"

View file

@ -168,10 +168,10 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><commit/></
# Leafref wrong
new "netconf get / config+state should fail"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get content=\"all\"><filter type=\"xpath\" select=\"/\"/></get></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-info><bad-element>x</bad-element></error-info><error-severity>error</error-severity><error-message>Leafref validation failed: No leaf x matching path /ex:sender-config/ex:name. Internal error, state callback returned invalid XML</error-message></rpc-error></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get content=\"all\"><filter type=\"xpath\" select=\"/\"/></get></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-info><bad-element>x</bad-element></error-info><error-severity>error</error-severity><error-message>Leafref validation failed: No leaf x matching path /ex:sender-config/ex:name in leafref.yang:[0-9]*. Internal error, state callback returned invalid XML</error-message></rpc-error></rpc-reply>]]>]]>$"
new "netconf get / state-only should fail"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get content=\"nonconfig\"><filter type=\"xpath\" select=\"/\"/></get></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-info><bad-element>x</bad-element></error-info><error-severity>error</error-severity><error-message>Leafref validation failed: No leaf x matching path /ex:sender-config/ex:name. Internal error, state callback returned invalid XML</error-message></rpc-error></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get content=\"nonconfig\"><filter type=\"xpath\" select=\"/\"/></get></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-info><bad-element>x</bad-element></error-info><error-severity>error</error-severity><error-message>Leafref validation failed: No leaf x matching path /ex:sender-config/ex:name in leafref.yang:[0-9]*. Internal error, state callback returned invalid XML</error-message></rpc-error></rpc-reply>]]>]]>$"
new "netconf get / config-only ok"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get content=\"config\"><filter type=\"xpath\" select=\"/\"/></get></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><data><sender-config xmlns=\"urn:example:example\"><name>y</name></sender-config></data></rpc-reply>]]>]]>$"

View file

@ -68,6 +68,12 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><commit/></
new "wrong filter type"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get><filter type='foo'><x xmlns='urn:example:filter'><y><a>1</a></y></x></filter></get></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-tag>operation-failed</error-tag><error-type>applicatio</error-type><error-severity>error</error-severity><error-message>filter type not supported</error-message><error-info>type</error-info></rpc-error></rpc-reply>]]>]]>$"
new "get-config subtree one (subtree implicit)"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-config><source><running/></source><filter><x xmlns='urn:example:filter'><y><a>1</a></y></x></filter></get-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><data><x xmlns=\"urn:example:filter\"><y><a>1</a><b>1</b></y></x></data></rpc-reply>]]>]]>$"
new "get subtree one (subtree implicit)"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get><filter><x xmlns='urn:example:filter'><y><a>1</a></y></x></filter></get></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><data><x xmlns=\"urn:example:filter\"><y><a>1</a><b>1</b></y></x></data></rpc-reply>]]>]]>$"
new "get-config subtree one"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-config><source><running/></source><filter type='subtree'><x xmlns='urn:example:filter'><y><a>1</a></y></x></filter></get-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><data><x xmlns=\"urn:example:filter\"><y><a>1</a><b>1</b></y></x></data></rpc-reply>]]>]]>$"

View file

@ -58,6 +58,10 @@ wait_backend
new "Netconf snd hello with xmldecl"
expecteof "$clixon_netconf -qf $cfg" 0 "<?xml version=\"1.0\" encoding=\"UTF-8\"?><hello $DEFAULTNS><capabilities><capability>urn:ietf:params:netconf:base:1.1</capability></capabilities></hello>]]>]]>" '^$' '^$'
# Hello, lowercase encoding
new "Netconf snd hello with xmldecl (lowercase encoding)"
expecteof "$clixon_netconf -qf $cfg" 0 "<?xml version=\"1.0\" encoding=\"utf-8\"?><hello $DEFAULTNS><capabilities><capability>urn:ietf:params:netconf:base:1.1</capability></capabilities></hello>]]>]]>" '^$' '^$'
new "Netconf snd hello without xmldecl"
expecteof "$clixon_netconf -qf $cfg" 0 "<hello $DEFAULTNS><capabilities><capability>urn:ietf:params:netconf:base:1.1</capability></capabilities></hello>]]>]]>" '^$' '^$'

View file

@ -368,6 +368,15 @@ module pattern{
'[0-9]|25[0-5])$';
}
}
leaf p47 {
description "draft-wwlh-netconf-list-pagination-00 module example-social";
type string {
length "1..80";
pattern '.*[\n].*' {
modifier invert-match;
}
}
}
}
}
EOF
@ -741,6 +750,11 @@ testrun "p$pnr" true '255.149.90.121'
testrun "p$pnr" true '251.148.80.69'
testrun "p$pnr" false '248:197.7.89/8'
let pnr=47 # '.*[\n].*
testrun "p$pnr" true 'Ensure all nights are cold'
testrun "p$pnr" false 'kalle foo'
testrun "p$pnr" false '01234567890123456789012345678901234567890123456789012345678901234567890123456789zzz'
# CLI tests
new "CLI tests for RFC7950 Sec 9.4.7 ex 2 AB"
expectpart "$($clixon_cli -1f $cfg -l o set c rfc2 AB)" 0 '^$'

View file

@ -23,8 +23,6 @@ startupdb=$dir/startup_db
RESTCONFDBG=$DBG
RCPROTO=http # no ssl here
RESTCONFDIR=$(dirname $(which clixon_restconf))
# log-destination in restconf xml: syslog or file
: ${LOGDST:=syslog}
# Set daemon command-line to -f
@ -54,7 +52,6 @@ cat <<EOF > $cfg
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_BACKEND_REGEXP>example_backend.so$</CLICON_BACKEND_REGEXP>
<CLICON_RESTCONF_DIR>/usr/local/lib/$APPNAME/restconf</CLICON_RESTCONF_DIR>
<CLICON_RESTCONF_INSTALLDIR>$RESTCONFDIR</CLICON_RESTCONF_INSTALLDIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>

View file

@ -34,8 +34,6 @@ startupdb=$dir/startup_db
RESTCONFDBG=$DBG
RCPROTO=http # no ssl here
RESTCONFDIR=$(dirname $(which clixon_restconf))
INVALIDADDR=251.1.1.1 # used by fourth usecase as invalid
# log-destination in restconf xml: syslog or file
@ -68,7 +66,6 @@ cat <<EOF > $cfg
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_BACKEND_REGEXP>example_backend.so$</CLICON_BACKEND_REGEXP>
<CLICON_RESTCONF_DIR>/usr/local/lib/$APPNAME/restconf</CLICON_RESTCONF_DIR>
<CLICON_RESTCONF_INSTALLDIR>$RESTCONFDIR</CLICON_RESTCONF_INSTALLDIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>

View file

@ -117,7 +117,7 @@ new "when get config"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><data><whenex xmlns=\"urn:example:clixon\"><type>direct</type><name>r2</name><static-routes/></whenex><whenex xmlns=\"urn:example:clixon\"><type>static</type><name>r1</name><static-routes/></whenex></data></rpc-reply>]]>]]>$"
new "when: validate fail"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>Failed WHEN condition of static-routes in module example</error-message></rpc-error></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>Failed WHEN condition of static-routes in module example (WHEN xpath is ../type='static')</error-message></rpc-error></rpc-reply>]]>]]>$"
new "when: discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><discard-changes/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"

View file

@ -127,6 +127,10 @@ expecteof "$clixon_util_xml -ol o" 255 '<?xml verion="1.0"?><a/>' '' 2> /dev/nul
new "XMLdecl version + encoding"
expecteof "$clixon_util_xml -o" 0 '<?xml version="1.0" encoding="UTF-8"?><a/>' '<a/>'
# XML processors SHOULD match character encoding names in a case-insensitive way
new "XMLdecl encoding case-insensitive"
expecteof "$clixon_util_xml -o" 0 '<?xml version="1.0" encoding="utf-8"?><a/>' '<a/>'
new "XMLdecl version + wrong encoding"
expecteof "$clixon_util_xml -o" 255 '<?xml version="1.0" encoding="UTF-16"?><a/>' '' 2> /dev/null

View file

@ -124,7 +124,7 @@ new "Set site to fie which invalidates the when contains"
expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><top xmlns=\"urn:example:clixon\"><class>fie</class></top></config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>"
new "netconf validate not OK"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>Failed WHEN condition of site in module example</error-message></rpc-error></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>Failed WHEN condition of site in module example (WHEN xpath is contains(../../class,'foo') or contains(../../class,'bar'))</error-message></rpc-error></rpc-reply>]]>]]>$"
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><discard-changes/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
@ -140,13 +140,13 @@ new "Change type to atm"
expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><interface xmlns=\"urn:example:clixon\"><name>e0</name><type>atm</type></interface></config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>"
new "netconf validate not OK (mtu not allowed)"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>Failed augmented 'when' condition 'derived-from(type, \"ex:ethernet\")' of node 'mtu' in module 'example'</error-message></rpc-error></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>Failed WHEN condition of mtu in module example (WHEN xpath is derived-from(type, \"ex:ethernet\"))</error-message></rpc-error></rpc-reply>]]>]]>$"
new "Change type to ethernet (self)"
expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><interface xmlns=\"urn:example:clixon\"><name>e0</name><type>ethernet</type></interface></config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>"
new "netconf validate not OK (mtu not allowed on self)"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>Failed augmented 'when' condition 'derived-from(type, \"ex:ethernet\")' of node 'mtu' in module 'example'</error-message></rpc-error></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>Failed WHEN condition of mtu in module example (WHEN xpath is derived-from(type, \"ex:ethernet\"))</error-message></rpc-error></rpc-reply>]]>]]>$"
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><discard-changes/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
@ -162,7 +162,7 @@ new "Change type to atm"
expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><interface xmlns=\"urn:example:clixon\"><name>e0</name><type>atm</type></interface></config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>"
new "netconf validate not OK (crc not allowed)"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>Failed augmented 'when' condition 'derived-from-or-self(type, \"ex:ethernet\")' of node 'crc' in module 'example'</error-message></rpc-error></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>Failed WHEN condition of crc in module example (WHEN xpath is derived-from-or-self(type, \"ex:ethernet\"))</error-message></rpc-error></rpc-reply>]]>]]>$"
new "Change type to ethernet (self)"
expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><interface xmlns=\"urn:example:clixon\"><name>e0</name><type>ethernet</type></interface></config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>"

View file

@ -169,7 +169,7 @@ case $release in
native)
$sshcmd sudo yum install -y libevent openssl
$sshcmd sudo yum install -y libevent-devel openssl-devel
$sshcmd sudo dnf config-manager --set-enabled powertools
$sshcmd sudo yum-config-manager --enable powertools
$sshcmd sudo yum install -y libnghttp2-devel
;;
esac
@ -214,7 +214,6 @@ case $release in
$sshcmd sudo apt install -y nginx
;;
native)
# $sshcmd sudo apt install -y libevent-2.1
$sshcmd sudo apt install -y libssl-dev
$sshcmd sudo apt install -y libevent-dev # evhtp
$sshcmd sudo apt install -y libnghttp2-dev # nghttp2

View file

@ -91,7 +91,7 @@ APPSRC += clixon_util_path.c
APPSRC += clixon_util_datastore.c
APPSRC += clixon_util_regexp.c
APPSRC += clixon_util_socket.c
# APPSRC += clixon_util_validate.c
APPSRC += clixon_util_validate.c
APPSRC += clixon_netconf_ssh_callhome.c
APPSRC += clixon_netconf_ssh_callhome_client.c
ifdef with_restconf

View file

@ -41,7 +41,7 @@ datarootdir = @datarootdir@
# See also OPT_YANG_INSTALLDIR for the standard yang files
YANG_INSTALLDIR = @YANG_INSTALLDIR@
YANGSPECS = clixon-config@2021-05-20.yang # 5.2
YANGSPECS = clixon-config@2021-07-11.yang # 5.3
YANGSPECS += clixon-lib@2021-03-08.yang # 5.1
YANGSPECS += clixon-rfc5277@2008-07-01.yang
YANGSPECS += clixon-xml-changelog@2019-03-21.yang

View file

@ -1 +0,0 @@
clixon-config@2021-03-08.yang

View file

@ -43,12 +43,33 @@ module clixon-config {
***** END LICENSE BLOCK *****";
revision 2021-07-11 {
description
"Added option:
CLICON_SYSTEM_CAPABILITIES
Removed default value:
CLICON_RESTCONF_INSTALLDIR
Marked as obsolete:
CLICON_YANG_LIST_CHECK
(Will be) Released in Clixon 5.3";
}
revision 2021-05-20 {
description
"Added option:
CLICON_RESTCONF_USER
CLICON_RESTCONF_PRIVILEGES
CLICON_RESTCONF_INSTALLDIR
CLICON_RESTCONF_STARTUP_DONTUPDATE
CLICON_NETCONF_MESSAGE_ID_OPTIONAL
Released in Clixon 5.2";
}
revision 2021-03-08 {
description
"Added option:
CLICON_NETCONF_HELLO_OPTIONAL
CLICON_CLI_AUTOCLI_EXCLUDE
CLICON_XMLDB_UPGRADE_CHECKOLD";
CLICON_XMLDB_UPGRADE_CHECKOLD
Released in Clixon 5.1";
}
revision 2020-12-30 {
description
@ -171,6 +192,10 @@ module clixon-config {
"Commit startup configuration into running state
After reboot when no persistent running db exists";
}
enum running-startup{
description
"First try running db, if it is empty try startup db.";
}
}
}
typedef datastore_format{
@ -406,7 +431,11 @@ module clixon-config {
"If false, skip Yang list check sanity checks from RFC 7950, Sec 7.8.2:
The 'key' statement, which MUST be present if the list represents configuration.
Some yang specs seem not to fulfil this. However, if you reset this, there may
be follow-up errors due to code that assumes a configuration list has keys";
be follow-up errors due to code that assumes a configuration list has keys
Marked as obsolete since the observation above seemed to be related to the
yang-data extension in RFC8040 allows non-key lists. This has been implemented
by a YANG_FLAG_NOKEY yang flag mechanism";
status obsolete;
}
leaf CLICON_YANG_UNKNOWN_ANYDATA{
type boolean;
@ -421,6 +450,18 @@ module clixon-config {
only loading from startup but may occur in other circumstances as well. This
means that sanity checks of erroneous XML/JSON may not be properly signalled.";
}
leaf CLICON_SYSTEM_CAPABILITIES {
type boolean;
default false;
description
"Enable module ietf-system-capabilities and ietf-notification-capabilities
Note: There are several dependencies:
- ietf-yang-library revision 2019-01-04 is REQUIRED
- nacm
- ietf-yang-structure-ext.yang,
- ietf-yang-instance-data
see draft-ietf-netconf-notification-capabilities-17";
}
leaf CLICON_BACKEND_DIR {
type string;
description
@ -451,6 +492,16 @@ module clixon-config {
is returned, which conforms to the RFC.
Note this applies only to external NETCONF, not the internal (IPC) netconf";
}
leaf CLICON_NETCONF_MESSAGE_ID_OPTIONAL {
type boolean;
default false;
description
"This option relates to RFC 6241 Sec 4.1 <rpc> Element
The <rpc> element has a mandatory attribute 'message-id', which is a
string chosen by the sender of the RPC.
If true, an RPC can be sent without a message-id.
This applies to both external NETCONF and internal (IPC) netconf";
}
leaf CLICON_RESTCONF_DIR {
type string;
description
@ -470,7 +521,33 @@ module clixon-config {
Note: Obsolete, use fcgi-socket in clixon-restconf.yang instead";
status obsolete;
}
leaf CLICON_RESTCONF_INSTALLDIR {
type string;
description
"If set, path to dir of clixon-restconf daemon binary as used by backend if
started internally (run-time).
If this path is not set, clixon_restconf will be looked for according to
configured installdir: $(sbindir) (install-time)
Since programs can be moved around at install/cross-compile time the installed
dir may be difficult to know at install time, which is the reason why
CLICON_RESTCONF_INSTALLDIR exists, in order to override the Makefile
installdir.
Note on the installdir, DESTDIR is not included since according to man pages:
by specifying DESTDIR should not change the operation of the software in
any way, so its value should not be included in any file contents. ";
}
leaf CLICON_RESTCONF_STARTUP_DONTUPDATE {
type boolean;
default false;
description
"According to RFC 8040 Sec 1.4:
If the NETCONF server supports :startup, the RESTCONF server MUST automatically
update the [...] startup configuration [...] as a consequence of a RESTCONF
edit operation.
Setting this option disables this behaviour, ie the startup configuration is NOT
automatically updated.
If this option is false, the startup is autoamtically updated following the RFC";
}
leaf CLICON_RESTCONF_PRETTY {
type boolean;
default true;
@ -486,6 +563,26 @@ module clixon-config {
Note: Obsolete, use pretty in clixon-restconf.yang instead";
status obsolete;
}
leaf CLICON_RESTCONF_USER {
type string;
description
"Run clixon_daemon as this user
When drop privileges is used, the daemon will drop privileges to this user.
In pre-5.2 code this was configured as compile-time constant WWWUSER with
default value www-data
See also CLICON_PRIVILEGES setting";
default www-data;
}
leaf CLICON_RESTCONF_PRIVILEGES {
type priv_mode;
default drop_perm;
description
"Restconf privileges mode.
If drop_perm or drop_temp then drop privileges to CLICON_RESTCONF_USER.
If the platform does not support getresuid and accompanying functions, the mode
must be set to 'none'.
";
}
leaf CLICON_CLI_DIR {
type string;
description
@ -557,6 +654,7 @@ module clixon-config {
<CLICON_CLI_AUTOCLI_EXCLUDE>clixon-restconf</CLICON_CLI_AUTOCLI_EXCLUDE>
means generate autocli for all models except clixon-restconf.yang
The value can be a list of space separated module names";
default "clixon-restconf";
}
leaf CLICON_CLI_VARONLY {
type int32;
@ -706,7 +804,7 @@ module clixon-config {
user (eg datastores).
It also sets the backend unix socket owner to this user, but its group
is set by CLICON_SOCK_GROUP.
See also CLICON_PRIVILEGES setting";
See also CLICON_BACKEND_PRIVILEGES setting";
}
leaf CLICON_BACKEND_PRIVILEGES {
type priv_mode;

View file

@ -1,180 +0,0 @@
module clixon-lib {
yang-version 1.1;
namespace "http://clicon.org/lib";
prefix cl;
organization
"Clicon / Clixon";
contact
"Olof Hagsand <olof@hagsand.se>";
description
"Clixon Netconf extensions for communication between clients and backend.
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON
Licensed under the Apache License, Version 2.0 (the \"License\");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an \"AS IS\" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the \"GPL\"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****";
revision 2020-12-30 {
description
"Changed: RPC process-control output parameter status to pid";
}
revision 2020-12-08 {
description
"Added: autocli-op extension.
rpc process-control for process/daemon management
Released in clixon 4.9";
}
revision 2020-04-23 {
description
"Added: stats RPC for clixon XML and memory statistics.
Added: restart-plugin RPC for restarting individual plugins without restarting backend.";
}
revision 2019-08-13 {
description
"No changes (reverted change)";
}
revision 2019-06-05 {
description
"ping rpc added for liveness";
}
revision 2019-01-02 {
description
"Released in Clixon 3.9";
}
typedef service-operation {
type enumeration {
enum start {
description
"Start if not already running";
}
enum stop {
description
"Stop if running";
}
enum restart {
description
"Stop if running, then start";
}
enum status {
description
"Check status";
}
}
description
"Common operations that can be performed on a service";
}
extension autocli-op {
description
"Takes an argument an operation defing how to modify the clispec at
this point in the YANG tree for the automated generated CLI.
Note that this extension is only used in clixon_cli.
Operations is expected to be extended, but the following operations are defined:
- hide This command is active but not shown by ? or TAB";
argument cliop;
}
rpc debug {
description "Set debug level of backend.";
input {
leaf level {
type uint32;
}
}
}
rpc ping {
description "Check aliveness of backend daemon.";
}
rpc stats {
description "Clixon XML statistics.";
output {
container global{
description "Clixon global statistics";
leaf xmlnr{
description "Number of XML objects: number of residing xml/json objects
in the internal 'cxobj' representation.";
type uint64;
}
}
list datastore{
description "Datastore statistics";
key "name";
leaf name{
description "name of datastore (eg running).";
type string;
}
leaf nr{
description "Number of XML objects. That is number of residing xml/json objects
in the internal 'cxobj' representation.";
type uint64;
}
leaf size{
description "Size in bytes of internal datastore cache of datastore tree.";
type uint64;
}
}
}
}
rpc restart-plugin {
description "Restart specific backend plugins.";
input {
leaf-list plugin {
description "Name of plugin to restart";
type string;
}
}
}
rpc process-control {
description
"Control a specific process or daemon: start/stop, etc.
This is for direct managing of a process by the backend.
Alternatively one can manage a daemon via systemd, containerd, kubernetes, etc.";
input {
leaf name {
description "Name of process";
type string;
mandatory true;
}
leaf operation {
type service-operation;
mandatory true;
description
"One of the strings 'start', 'stop', 'restart', or 'status'.";
}
}
output {
leaf pid {
description "Process-id of running process or 0 if not running
Value is only valid for operation status";
type uint32;
}
}
}
}

View file

@ -1,221 +0,0 @@
module clixon-restconf {
yang-version 1.1;
namespace "http://clicon.org/restconf";
prefix "clrc";
import ietf-inet-types {
prefix inet;
}
organization
"Clixon";
contact
"Olof Hagsand <olof@hagsand.se>";
description
"This YANG module provides a data-model for the Clixon RESTCONF daemon.
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON
Licensed under the Apache License, Version 2.0 (the \"License\");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an \"AS IS\" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the \"GPL\"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****";
revision 2021-03-15 {
description
"make authentication-type none a feature
Added flag to enable core dumps";
}
revision 2020-12-30 {
description
"Added: debug field
Added 'none' as default value for auth-type
Changed http-auth-type enum from 'password' to 'user'";
}
revision 2020-10-30 {
description
"Initial release";
}
feature fcgi {
description
"This feature indicates that the restconf server supports the fast-cgi reverse
proxy solution.
That is, a reverse proxy is the HTTP front-end and the restconf daemon listens
to a fcgi socket.
The alternative is the internal HTTP solution using evhtp.";
}
feature allow-auth-none {
description
"This feature allows the use of authentication-type none.";
}
typedef http-auth-type {
type enumeration {
enum none {
if-feature "allow-auth-none";
description
"Incoming message are set to authenticated by default. No ca-auth callback is called,
Authenticated user is set to special user 'none'.
Typically assumes NACM is not enabled.";
}
enum client-certificate {
description
"TLS client certificate validation is made on each incoming message. If it passes
the authenticated user is extracted from the SSL_CN parameter
The ca-auth callback can be used to revise this behavior.";
}
enum user {
description
"User-defined authentication as defined by the ca-auth callback.
One example is some form of password authentication, such as basic auth.";
}
}
description
"Enumeration of HTTP authorization types.";
}
grouping clixon-restconf{
description
"HTTP RESTCONF configuration.";
leaf enable {
type boolean;
default "false";
description
"Enables RESTCONF functionality.
Note that starting/stopping of a restconf daemon is different from it being
enabled or not.
For example, if the restconf daemon is under systemd management, the restconf
daemon will only start if enable=true.";
}
leaf auth-type {
type http-auth-type;
description
"The authentication type.
Note client-certificate applies only if ssl-enable is true and socket has ssl";
default user;
}
leaf debug {
description
"Set debug level of restconf daemon.
0 is no debug, 1 is debugging, more is detailed debug.
Debug logs will be directed to syslog with
ident: clixon_restconf and PID
facility: LOG_USER
level: LOG_DEBUG";
type uint32;
default 0;
}
leaf enable-core-dump {
description
"enable core dumps.
this is a no-op on systems that don't support it.";
type boolean;
default false;
}
leaf pretty {
type boolean;
default true;
description
"Restconf return value pretty print.
Restconf clients may add HTTP header:
Accept: application/yang-data+json, or
Accept: application/yang-data+xml
to get return value in XML or JSON.
RFC 8040 examples print XML and JSON in pretty-printed form.
Setting this value to false makes restconf return not pretty-printed
which may be desirable for performance or tests
This replaces the CLICON_RESTCONF_PRETTY option in clixon-config.yang";
}
/* From this point only specific options
* First fcgi-specific options
*/
leaf fcgi-socket {
if-feature fcgi; /* Set by default by fcgi clixon_restconf daemon */
type string;
default "/www-data/fastcgi_restconf.sock";
description
"Path to FastCGI unix socket. Should be specified in webserver
Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock
Only if with-restconf=fcgi, NOT evhtp
This replaces CLICON_RESTCONF_PATH option in clixon-config.yang";
}
/* Second, evhtp-specific options */
leaf server-cert-path {
type string;
description
"Path to server certificate file.
Note only applies if socket has ssl enabled";
}
leaf server-key-path {
type string;
description
"Path to server key file
Note only applies if socket has ssl enabled";
}
leaf server-ca-cert-path {
type string;
description
"Path to server CA cert file
Note only applies if socket has ssl enabled";
}
list socket {
description
"List of server sockets that the restconf daemon listens to";
key "namespace address port";
leaf namespace {
type string;
description
"Network namespace.
On platforms where namespaces are not suppported, 'default'
Default value can be changed by RESTCONF_NETNS_DEFAULT";
}
leaf address {
type inet:ip-address;
description "IP address to bind to";
}
leaf port {
type inet:port-number;
description "TCP port to bind to";
}
leaf ssl {
type boolean;
default true;
description "Enable for HTTPS otherwise HTTP protocol";
}
}
}
container restconf {
description
"This presence is strictly not necessary since the enable flag
in clixon-restconf is the flag bearing the actual semantics.
However, removing the presence leads to default config in all
clixon installations, even those which do not use backend-started restconf.
One could see this as mostly cosmetically annoying.
Alternative would be to make the inclusion of this yang conditional.";
presence "Enables RESTCONF";
uses clixon-restconf;
}
}

View file

@ -50,6 +50,7 @@ YANGSPECS += ietf-restconf-monitoring@2017-01-26.yang
YANGSPECS += ietf-yang-library@2019-01-04.yang
YANGSPECS += ietf-yang-types@2013-07-15.yang
YANGSPECS += ietf-datastores@2018-02-14.yang
YANGSPECS += ietf-yang-patch@2017-02-22.yang
all:

View file

@ -0,0 +1,390 @@
module ietf-yang-patch {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:ietf-yang-patch";
prefix "ypatch";
import ietf-restconf { prefix rc; }
organization
"IETF NETCONF (Network Configuration) Working Group";
contact
"WG Web: <https://datatracker.ietf.org/wg/netconf/>
WG List: <mailto:netconf@ietf.org>
Author: Andy Bierman
<mailto:andy@yumaworks.com>
Author: Martin Bjorklund
<mailto:mbj@tail-f.com>
Author: Kent Watsen
<mailto:kwatsen@juniper.net>";
description
"This module contains conceptual YANG specifications
for the YANG Patch and YANG Patch Status data structures.
Note that the YANG definitions within this module do not
represent configuration data of any kind.
The YANG grouping statements provide 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 8072; see
the RFC itself for full legal notices.";
revision 2017-02-22 {
description
"Initial revision.";
reference
"RFC 8072: YANG Patch Media Type.";
}
typedef target-resource-offset {
type string;
description
"Contains a data resource identifier string representing
a sub-resource within the target resource.
The document root for this expression is the
target resource that is specified in the
protocol operation (e.g., the URI for the PATCH request).
This string is encoded according to the same rules as those
for a data resource identifier in a RESTCONF request URI.";
reference
"RFC 8040, Section 3.5.3.";
}
rc:yang-data "yang-patch" {
uses yang-patch;
}
rc:yang-data "yang-patch-status" {
uses yang-patch-status;
}
grouping yang-patch {
description
"A grouping that contains a YANG container representing the
syntax and semantics of a YANG Patch edit request message.";
container yang-patch {
description
"Represents a conceptual sequence of datastore edits,
called a patch. Each patch is given a client-assigned
patch identifier. Each edit MUST be applied
in ascending order, and all edits MUST be applied.
If any errors occur, then the target datastore MUST NOT
be changed by the YANG Patch operation.
It is possible for a datastore constraint violation to occur
due to any node in the datastore, including nodes not
included in the 'edit' list. Any validation errors MUST
be reported in the reply message.";
reference
"RFC 7950, Section 8.3.";
leaf patch-id {
type string;
mandatory true;
description
"An arbitrary string provided by the client to identify
the entire patch. Error messages returned by the server
that pertain to this patch will be identified by this
'patch-id' value. A client SHOULD attempt to generate
unique 'patch-id' values to distinguish between
transactions from multiple clients in any audit logs
maintained by the server.";
}
leaf comment {
type string;
description
"An arbitrary string provided by the client to describe
the entire patch. This value SHOULD be present in any
audit logging records generated by the server for the
patch.";
}
list edit {
key edit-id;
ordered-by user;
description
"Represents one edit within the YANG Patch request message.
The 'edit' list is applied in the following manner:
- The first edit is conceptually applied to a copy
of the existing target datastore, e.g., the
running configuration datastore.
- Each ascending edit is conceptually applied to
the result of the previous edit(s).
- After all edits have been successfully processed,
the result is validated according to YANG constraints.
- If successful, the server will attempt to apply
the result to the target datastore.";
leaf edit-id {
type string;
description
"Arbitrary string index for the edit.
Error messages returned by the server that pertain
to a specific edit will be identified by this value.";
}
leaf operation {
type enumeration {
enum create {
description
"The target data node is created using the supplied
value, only if it does not already exist. The
'target' leaf identifies the data node to be
created, not the parent data node.";
}
enum delete {
description
"Delete the target node, only if the data resource
currently exists; otherwise, return an error.";
}
enum insert {
description
"Insert the supplied value into a user-ordered
list or leaf-list entry. The target node must
represent a new data resource. If the 'where'
parameter is set to 'before' or 'after', then
the 'point' parameter identifies the insertion
point for the target node.";
}
enum merge {
description
"The supplied value is merged with the target data
node.";
}
enum move {
description
"Move the target node. Reorder a user-ordered
list or leaf-list. The target node must represent
an existing data resource. If the 'where' parameter
is set to 'before' or 'after', then the 'point'
parameter identifies the insertion point to move
the target node.";
}
enum replace {
description
"The supplied value is used to replace the target
data node.";
}
enum remove {
description
"Delete the target node if it currently exists.";
}
}
mandatory true;
description
"The datastore operation requested for the associated
'edit' entry.";
}
leaf target {
type target-resource-offset;
mandatory true;
description
"Identifies the target data node for the edit
operation. If the target has the value '/', then
the target data node is the target resource.
The target node MUST identify a data resource,
not the datastore resource.";
}
leaf point {
when "(../operation = 'insert' or ../operation = 'move')"
+ "and (../where = 'before' or ../where = 'after')" {
description
"This leaf only applies for 'insert' or 'move'
operations, before or after an existing entry.";
}
type target-resource-offset;
description
"The absolute URL path for the data node that is being
used as the insertion point or move point for the
target of this 'edit' entry.";
}
leaf where {
when "../operation = 'insert' or ../operation = 'move'" {
description
"This leaf only applies for 'insert' or 'move'
operations.";
}
type enumeration {
enum before {
description
"Insert or move a data node before the data resource
identified by the 'point' parameter.";
}
enum after {
description
"Insert or move a data node after the data resource
identified by the 'point' parameter.";
}
enum first {
description
"Insert or move a data node so it becomes ordered
as the first entry.";
}
enum last {
description
"Insert or move a data node so it becomes ordered
as the last entry.";
}
}
default last;
description
"Identifies where a data resource will be inserted
or moved. YANG only allows these operations for
list and leaf-list data nodes that are
'ordered-by user'.";
}
anydata value {
when "../operation = 'create' "
+ "or ../operation = 'merge' "
+ "or ../operation = 'replace' "
+ "or ../operation = 'insert'" {
description
"The anydata 'value' is only used for 'create',
'merge', 'replace', and 'insert' operations.";
}
description
"Value used for this edit operation. The anydata 'value'
contains the target resource associated with the
'target' leaf.
For example, suppose the target node is a YANG container
named foo:
container foo {
leaf a { type string; }
leaf b { type int32; }
}
The 'value' node contains one instance of foo:
<value>
<foo xmlns='example-foo-namespace'>
<a>some value</a>
<b>42</b>
</foo>
</value>
";
}
}
}
} // grouping yang-patch
grouping yang-patch-status {
description
"A grouping that contains a YANG container representing the
syntax and semantics of a YANG Patch Status response
message.";
container yang-patch-status {
description
"A container representing the response message sent by the
server after a YANG Patch edit request message has been
processed.";
leaf patch-id {
type string;
mandatory true;
description
"The 'patch-id' value used in the request.";
}
choice global-status {
description
"Report global errors or complete success.
If there is no case selected, then errors
are reported in the 'edit-status' container.";
case global-errors {
uses rc:errors;
description
"This container will be present if global errors that
are unrelated to a specific edit occurred.";
}
leaf ok {
type empty;
description
"This leaf will be present if the request succeeded
and there are no errors reported in the 'edit-status'
container.";
}
}
container edit-status {
description
"This container will be present if there are
edit-specific status responses to report.
If all edits succeeded and the 'global-status'
returned is 'ok', then a server MAY omit this
container.";
list edit {
key edit-id;
description
"Represents a list of status responses,
corresponding to edits in the YANG Patch
request message. If an 'edit' entry was
skipped or not reached by the server,
then this list will not contain a corresponding
entry for that edit.";
leaf edit-id {
type string;
description
"Response status is for the 'edit' list entry
with this 'edit-id' value.";
}
choice edit-status-choice {
description
"A choice between different types of status
responses for each 'edit' entry.";
leaf ok {
type empty;
description
"This 'edit' entry was invoked without any
errors detected by the server associated
with this edit.";
}
case errors {
uses rc:errors;
description
"The server detected errors associated with the
edit identified by the same 'edit-id' value.";
}
}
}
}
}
} // grouping yang-patch-status
}