* Stricter handling of multi-namespace handling
* This occurs in cases where there are more than one XML namespaces in a config tree, such as `augment`:ed trees. * Affects all parts of the system, including datastore, backend, restconf and cli. * Invalid api-path syntax (eg non-matching yang) error changed from 412 operation-failed to 400 Bad request invalid-value, or unknown-element.
This commit is contained in:
parent
a547b3f31d
commit
d9136c8972
22 changed files with 777 additions and 236 deletions
21
CHANGELOG.md
21
CHANGELOG.md
|
|
@ -1,6 +1,6 @@
|
|||
# Clixon Changelog
|
||||
|
||||
## 4.2.0 (Expected: September)
|
||||
## 4.2.0 (Expected: October)
|
||||
|
||||
### Major New features
|
||||
* Backend daemon can drop privileges after initialization to run as non-privileged user
|
||||
|
|
@ -10,17 +10,15 @@
|
|||
* If dropped temporary, you can restore privileges with `restore_priv()`
|
||||
|
||||
### API changes on existing features (you may need to change your code)
|
||||
* Stricter handling of multi-namespace handling
|
||||
* This occurs in cases where there are more than one XML namespaces in a config tree, such as `augment`:ed trees.
|
||||
* Affects all parts of the system, including datastore, backend, restconf and cli.
|
||||
* Examples of a mandated stricter usage of a simple augment `b` of symbol `a`. Assume `a` is in module `mod1` with namespace `urn:example:a` and `b` is in module `mod2` with namespace `urn:example:b`:
|
||||
* RESTCONF: `GET http://localhost/restconf/data/mod1:a/mod2:b`
|
||||
* NETCONF: `<a xmlns="urn:example:a" xmlns:b="urn:example:b"><b:b>42</b:b></a>`
|
||||
* XPATH (in edit-config filter): `<filter type="xpath" select="a:a/b:b" xmlns:a="urn:example:a" xmlns:b="urn:example:b"/>`
|
||||
* RESTCONF error reporting
|
||||
* Invalid api-path syntax (eg non-matching yang) error changed from 412 operation-failed to 400 unknown-element / invalid-value. For example, change from
|
||||
```
|
||||
HTTP/1.1 412 Precondition Failed
|
||||
{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"No such yang module: badmodule"}}}
|
||||
```
|
||||
to:
|
||||
```
|
||||
HTTP/1.1 400 Bad Request
|
||||
{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"No such yang module: badmodule"}}}
|
||||
```
|
||||
* Invalid api-path syntax (eg non-matching yang) error changed from 412 operation-failed to 400 Bad request invalid-value, or unknown-element.
|
||||
* Typical installation should now add a `clicon` user (as well as group)
|
||||
* New clixon-config@2019-09-11.yang revision
|
||||
* Added: CLICON_BACKEND_USER: drop of privileges to user,
|
||||
|
|
@ -33,6 +31,7 @@
|
|||
* Configure and test modification for better Freebsd port
|
||||
|
||||
### Corrected Bugs
|
||||
* See "Stricter handling of multi-namespace handling" in API-changes above.
|
||||
* Hello netconf candidate capability misspelled, mentioned in [Can clixon_netconf receive netconf packets as a server? #93](https://github.com/clicon/clixon/issues/93)
|
||||
* [Cannot write to config using restconf example #91](https://github.com/clicon/clixon/issues/91)
|
||||
* Updated restconf documentation (the example was wrong)
|
||||
|
|
|
|||
|
|
@ -527,11 +527,10 @@ from_client_edit_config(clicon_handle h,
|
|||
if (xml_spec(xc) != NULL)
|
||||
xml_spec_set(xc, NULL);
|
||||
/* Populate XML with Yang spec (why not do this in parser?)
|
||||
* Maybe validate xml here as in text_modify_top?
|
||||
*/
|
||||
if (xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
goto done;
|
||||
|
||||
/* Maybe validate xml here as in text_modify_top? */
|
||||
if (xml_apply(xc, CX_ELMNT, xml_non_config_data, &non_config) < 0)
|
||||
goto done;
|
||||
if (non_config){
|
||||
|
|
|
|||
|
|
@ -195,10 +195,12 @@ startup_common(clicon_handle h,
|
|||
goto ok;
|
||||
}
|
||||
if (msd){
|
||||
/* Here xt is old syntax */
|
||||
if ((ret = clixon_module_upgrade(h, xt, msd, cbret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
/* Here xt is new syntax */
|
||||
}
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_YANG, 0, "Yang spec not set");
|
||||
|
|
|
|||
|
|
@ -70,7 +70,6 @@
|
|||
#include "backend_commit.h"
|
||||
#include "backend_startup.h"
|
||||
|
||||
|
||||
/*! Merge db1 into db2 without commit
|
||||
* @retval -1 Error
|
||||
* @retval 0 Validation failed (with cbret set)
|
||||
|
|
@ -169,6 +168,7 @@ load_extraxml(clicon_handle h,
|
|||
int retval = -1;
|
||||
cxobj *xt = NULL;
|
||||
int fd = -1;
|
||||
yang_stmt *yspec = NULL;
|
||||
|
||||
if (filename == NULL)
|
||||
return 1;
|
||||
|
|
@ -176,7 +176,8 @@ load_extraxml(clicon_handle h,
|
|||
clicon_err(OE_UNIX, errno, "open(%s)", filename);
|
||||
goto done;
|
||||
}
|
||||
if (xml_parse_file(fd, "</config>", NULL, &xt) < 0)
|
||||
yspec = clicon_dbspec_yang(h);
|
||||
if (xml_parse_file(fd, "</config>", yspec, &xt) < 0)
|
||||
goto done;
|
||||
/* Replace parent w first child */
|
||||
if (xml_rootchild(xt, 0, &xt) < 0)
|
||||
|
|
|
|||
|
|
@ -526,6 +526,7 @@ api_return_err(clicon_handle h,
|
|||
FCGX_FPrintF(r->out, "%s", cbuf_get(cb));
|
||||
FCGX_FPrintF(r->out, "}\r\n");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
clicon_err(OE_YANG, EINVAL, "Invalid media type %d", media);
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -165,22 +165,18 @@ api_data_get2(clicon_handle h,
|
|||
if ((cbpath = cbuf_new()) == NULL)
|
||||
goto done;
|
||||
cprintf(cbpath, "/");
|
||||
/* We know "data" is element pi-1 */
|
||||
if ((ret = api_path2xpath_cvv(pcvec, pi, yspec, cbpath, &namespace, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
clicon_err_reset();
|
||||
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
|
||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||
/* Create a namespace context for ymod as the default namespace to use with
|
||||
* xpath expressions */
|
||||
if ((nsc = cvec_new(0)) == NULL){
|
||||
clicon_err(OE_XML, errno, "cvec_new");
|
||||
goto done;
|
||||
}
|
||||
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||
/* We know "data" is element pi-1.
|
||||
* Translate api-path to xpath: xpath (cbpath) and namespace context (nsc)
|
||||
*/
|
||||
if ((ret = api_path2xpath_cvv2(pcvec, pi, yspec, cbpath, nsc, &xerr)) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
if (ret == 0){
|
||||
if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0)
|
||||
goto done;
|
||||
clicon_err_reset();
|
||||
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
|
||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||
|
|
@ -192,22 +188,15 @@ api_data_get2(clicon_handle h,
|
|||
}
|
||||
xpath = cbuf_get(cbpath);
|
||||
clicon_debug(1, "%s path:%s", __FUNCTION__, xpath);
|
||||
/* Create a namespace context for ymod as the default namespace to use with
|
||||
* xpath expressions */
|
||||
if ((nsc = xml_nsctx_init(NULL, namespace)) == NULL)
|
||||
goto done;
|
||||
switch (content){
|
||||
case CONTENT_CONFIG:
|
||||
ret = clicon_rpc_get(h, xpath, namespace, CONTENT_CONFIG, depth, &xret);
|
||||
break;
|
||||
case CONTENT_NONCONFIG:
|
||||
ret = clicon_rpc_get(h, xpath, namespace, CONTENT_NONCONFIG, depth, &xret);
|
||||
break;
|
||||
case CONTENT_ALL:
|
||||
ret = clicon_rpc_get(h, xpath, namespace, CONTENT_ALL, depth, &xret);
|
||||
ret = clicon_rpc_get_nsc(h, xpath, nsc, content, depth, &xret);
|
||||
break;
|
||||
default:
|
||||
clicon_err(OE_XML, EINVAL, "Invalid content attribute %d", content);
|
||||
goto done;
|
||||
break;
|
||||
}
|
||||
if (ret < 0){
|
||||
|
|
@ -291,14 +280,14 @@ api_data_get2(clicon_handle h,
|
|||
switch (media_out){
|
||||
case YANG_DATA_XML:
|
||||
for (i=0; i<xlen; i++){
|
||||
char *prefix, *namespace2; /* Same as namespace? */
|
||||
char *prefix;
|
||||
x = xvec[i];
|
||||
/* Some complexities in grafting namespace in existing trees to new */
|
||||
prefix = xml_prefix(x);
|
||||
if (xml_find_type_value(x, prefix, "xmlns", CX_ATTR) == NULL){
|
||||
if (xml2ns(x, prefix, &namespace2) < 0)
|
||||
if (xml2ns(x, prefix, &namespace) < 0)
|
||||
goto done;
|
||||
if (namespace2 && xmlns_set(x, prefix, namespace2) < 0)
|
||||
if (namespace && xmlns_set(x, prefix, namespace) < 0)
|
||||
goto done;
|
||||
}
|
||||
if (clicon_xml2cbuf(cbx, x, 0, pretty, -1) < 0) /* Dont print top object? */
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ int clicon_rpc_delete_config(clicon_handle h, char *db);
|
|||
int clicon_rpc_lock(clicon_handle h, char *db);
|
||||
int clicon_rpc_unlock(clicon_handle h, char *db);
|
||||
int clicon_rpc_get(clicon_handle h, char *xpath, char *namespace, netconf_content content, int32_t depth, cxobj **xret);
|
||||
int clicon_rpc_get_nsc(clicon_handle h, char *xpath, cvec *nsc, netconf_content content, int32_t depth, cxobj **xret);
|
||||
int clicon_rpc_close_session(clicon_handle h);
|
||||
int clicon_rpc_kill_session(clicon_handle h, int session_id);
|
||||
int clicon_rpc_validate(clicon_handle h, char *db);
|
||||
|
|
|
|||
|
|
@ -108,9 +108,17 @@ char *xml_name(cxobj *xn);
|
|||
int xml_name_set(cxobj *xn, char *name);
|
||||
char *xml_prefix(cxobj *xn);
|
||||
int xml_prefix_set(cxobj *xn, char *name);
|
||||
char *nscache_get(cxobj *x, char *prefix);
|
||||
int nscache_get_prefix(cxobj *x, char *namespace, char **prefix);
|
||||
cvec *nscache_get_all(cxobj *x);
|
||||
int nscache_set(cxobj *x, char *prefix, char *namespace);
|
||||
|
||||
int nscache_clear(cxobj *x);
|
||||
int nscache_replace(cxobj *x, cvec *ns);
|
||||
|
||||
int xml2ns(cxobj *x, char *localname, char **namespace);
|
||||
int xml2prefix(cxobj *xn, char *namespace, char **prefixp);
|
||||
|
||||
int xmlns_set(cxobj *x, char *prefix, char *namespace);
|
||||
cxobj *xml_parent(cxobj *xn);
|
||||
int xml_parent_set(cxobj *xn, cxobj *parent);
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ int xml_non_config_data(cxobj *xt, void *arg);
|
|||
int xml_spec_populate_rpc(clicon_handle h, cxobj *x, yang_stmt *yspec);
|
||||
int xml_spec_populate(cxobj *x, void *arg);
|
||||
int api_path2xpath_cvv(cvec *api_path, int offset, yang_stmt *yspec, cbuf *xpath, char **namespace, cxobj **xerr);
|
||||
int api_path2xpath_cvv2(cvec *api_path, int offset, yang_stmt *yspec, cbuf *xpath, cvec *nsc, cxobj **xerr);
|
||||
int api_path2xpath(char *api_path, yang_stmt *yspec, char **xpath, char **namespace);
|
||||
int api_path2xml(char *api_path, yang_stmt *yspec, cxobj *xtop,
|
||||
yang_class nodeclass, int strict, cxobj **xpathp, yang_stmt **ypathp);
|
||||
|
|
|
|||
|
|
@ -46,9 +46,9 @@
|
|||
* Types
|
||||
*/
|
||||
|
||||
/* Struct contataining module state differences between two modules or two
|
||||
/* Struct containing module state differences between two modules or two
|
||||
* revisions of same module.
|
||||
* This is in state of flux so it needss to be conatained and easily changed.
|
||||
* This is in state of flux so it needs to be contained and easily changed.
|
||||
*/
|
||||
typedef struct {
|
||||
cxobj *md_del; /* yang module state deletes */
|
||||
|
|
|
|||
|
|
@ -141,9 +141,15 @@ xml_copy_marked(cxobj *x0,
|
|||
int iskey;
|
||||
yang_stmt *yt;
|
||||
char *name;
|
||||
char *prefix;
|
||||
|
||||
assert(x0 && x1);
|
||||
yt = xml_spec(x0); /* can be null */
|
||||
/* Copy prefix*/
|
||||
if ((prefix = xml_prefix(x0)) != NULL)
|
||||
if (xml_prefix_set(x1, prefix) < 0)
|
||||
goto done;
|
||||
|
||||
/* Copy all attributes */
|
||||
x = NULL;
|
||||
while ((x = xml_child_each(x0, x, CX_ATTR)) != NULL) {
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@
|
|||
#include "clixon_json.h"
|
||||
#include "clixon_nacm.h"
|
||||
#include "clixon_netconf_lib.h"
|
||||
#include "clixon_yang_type.h"
|
||||
#include "clixon_yang_module.h"
|
||||
#include "clixon_xml_nsctx.h"
|
||||
#include "clixon_xml_map.h"
|
||||
|
|
@ -83,45 +84,6 @@
|
|||
#include "clixon_datastore_read.h"
|
||||
#include "clixon_datastore_tree.h"
|
||||
|
||||
/*! Replace all xmlns attributes in x0 with xmlns attributes in x1
|
||||
* This is an embryo of code to actually check if namespace binding is canonical
|
||||
* and if it is not, either return error or transform to canonical.
|
||||
* "Canonical" meaning comply to the yang module prefixes.
|
||||
* The current code does not really do anything useful
|
||||
*/
|
||||
static int
|
||||
replace_xmlns(cxobj *x0,
|
||||
cxobj *x1)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *x = NULL;
|
||||
cxobj *xcopy;
|
||||
int i;
|
||||
|
||||
for (i=0; i<xml_child_nr(x0); ){
|
||||
x = xml_child_i(x0, i);
|
||||
if (!isxmlns(x)){
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
xml_rm(x);
|
||||
xml_free(x);
|
||||
}
|
||||
x = NULL;
|
||||
while ((x = xml_child_each(x1, x, CX_ATTR)) != NULL) {
|
||||
/* split and only add xmlns= and xmlns:x= attributes! */
|
||||
if (!isxmlns(x))
|
||||
continue;
|
||||
if ((xcopy = xml_new(xml_name(x), x0, xml_spec(x))) == NULL)
|
||||
goto done;
|
||||
if (xml_copy(x, xcopy) < 0) /* recursion */
|
||||
goto done;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Given an attribute name and its expected namespace, find its value
|
||||
*
|
||||
* An attribute may have a prefix(or NULL). The routine finds the associated
|
||||
|
|
@ -171,6 +133,206 @@ attr_ns_value(cxobj *x,
|
|||
goto done;
|
||||
}
|
||||
|
||||
/*! Given a src node x0 and a target node x1, assign (optional) prefix and namespace
|
||||
* @param[in] x0 Source XML tree
|
||||
* @param[in] x1 Target XML tree
|
||||
* 1. Find N=namespace(x0)
|
||||
* 2. Detect if N is declared in x1 parent
|
||||
* 3. If yes, assign prefix to x1
|
||||
* 4. If no, create new prefix/namespace binding and assign that to x1p (x1 if x1p is root)
|
||||
* 5. Add prefix to x1, if any
|
||||
* 6. Ensure x1 cache is updated
|
||||
* @note switch use of x0 and x1 compared to datastore text_modify
|
||||
* @see xml2ns
|
||||
* XXX: fail handling: if (netconf_data_missing(cbret, NULL, "Data does not exist; cannot delete resource") < 0)
|
||||
goto done;
|
||||
|
||||
*/
|
||||
static int
|
||||
check_namespaces(cxobj *x0,
|
||||
cxobj *x1,
|
||||
cxobj *x1p)
|
||||
{
|
||||
int retval = -1;
|
||||
char *namespace = NULL;
|
||||
char *prefix0 = NULL;;
|
||||
char *prefix10 = NULL; /* extra just for malloc problem */
|
||||
char *prefix1 = NULL;;
|
||||
cvec *nsc0 = NULL;
|
||||
cvec *nsc = NULL;
|
||||
cxobj *xa = NULL;
|
||||
cxobj *x;
|
||||
int isroot;
|
||||
|
||||
/* XXX: need to identify root better than hiereustics and strcmp,... */
|
||||
isroot = xml_parent(x1p)==NULL &&
|
||||
strcmp(xml_name(x1p), "config") == 0 &&
|
||||
xml_prefix(x1p)==NULL;
|
||||
|
||||
/* 1. Find N=namespace(x0) */
|
||||
prefix0 = xml_prefix(x0);
|
||||
if (xml2ns(x0, prefix0, &namespace) < 0)
|
||||
goto done;
|
||||
if (namespace == NULL){
|
||||
clicon_err(OE_XML, ENOENT, "No namespace found for prefix:%s",
|
||||
prefix0?prefix0:"NULL");
|
||||
goto done;
|
||||
}
|
||||
/* 2. Detect if namespace is declared in x1:s parent */
|
||||
if (xml2prefix(x1p, namespace, &prefix10) == 1){
|
||||
if (prefix10){
|
||||
if ((prefix1 = strdup(prefix10)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "strdup");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
else
|
||||
prefix1 = NULL;
|
||||
/* 3. If yes, assign prefix to x1 */
|
||||
if (prefix1 && xml_prefix_set(x1, prefix1) < 0)
|
||||
goto done;
|
||||
/* And copy namespace context from parent to child */
|
||||
if ((nsc0 = nscache_get_all(x1p)) != NULL){
|
||||
if ((nsc = cvec_dup(nsc0)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cvec_dup");
|
||||
goto done;
|
||||
}
|
||||
nscache_replace(x1, nsc);
|
||||
}
|
||||
/* Just in case */
|
||||
if (nscache_set(x1, prefix1, namespace) < 0)
|
||||
goto done;
|
||||
}
|
||||
else{
|
||||
/* 4. If no, create new prefix/namespace binding and assign that to x1p
|
||||
* use modules own default prefix (some chance for clash)
|
||||
*/
|
||||
if (prefix0 == NULL && !isroot){
|
||||
assert(xml_spec(x1) != NULL);
|
||||
prefix0 = yang_find_myprefix(xml_spec(x1));
|
||||
}
|
||||
if (prefix0)
|
||||
if ((prefix1 = strdup(prefix0)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "strdup");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Add binding to x1p. We add to parent due to heurestics, so we dont
|
||||
* end up in adding it to large number of siblings
|
||||
*/
|
||||
if (isroot)
|
||||
x = x1;
|
||||
else
|
||||
x = x1p;
|
||||
if (nscache_set(x, prefix1, namespace) < 0)
|
||||
goto done;
|
||||
if (x == x1p){
|
||||
if ((nsc0 = nscache_get_all(x1p)) != NULL)
|
||||
if ((nsc = cvec_dup(nsc0)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cvec_dup");
|
||||
goto done;
|
||||
}
|
||||
/* Copy x1p cache to x1 */
|
||||
nscache_replace(x1, nsc);
|
||||
}
|
||||
/* Create xmlns attribute to x1p/x1 XXX same code v */
|
||||
if (prefix1){
|
||||
if ((xa = xml_new(prefix1, x, NULL)) == NULL)
|
||||
goto done;
|
||||
if (xml_prefix_set(xa, "xmlns") < 0)
|
||||
goto done;
|
||||
}
|
||||
else{
|
||||
if ((xa = xml_new("xmlns", x, NULL)) == NULL)
|
||||
goto done;
|
||||
}
|
||||
xml_type_set(xa, CX_ATTR);
|
||||
if (xml_value_set(xa, namespace) < 0)
|
||||
goto done;
|
||||
xml_sort(x, NULL); /* Ensure attr is first / XXX xml_insert? */
|
||||
|
||||
/* 5. Add prefix to x1, if any */
|
||||
if (prefix1 && xml_prefix_set(x1, prefix1) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* 6. Ensure x1 cache is updated (I think it is done w xmlns_set above) */
|
||||
retval = 0;
|
||||
done:
|
||||
if (prefix1)
|
||||
free(prefix1);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int
|
||||
check_identityref(cxobj *x0,
|
||||
cxobj *x1,
|
||||
cxobj *x1p,
|
||||
char *x1bstr,
|
||||
yang_stmt *y)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *yrestype = NULL;
|
||||
char *prefix = NULL;
|
||||
char *ns0 = NULL;
|
||||
char *ns1 = NULL;
|
||||
cxobj *xa;
|
||||
cxobj *x;
|
||||
int isroot;
|
||||
|
||||
/* XXX: need to identify root better than hiereustics and strcmp,... */
|
||||
isroot = xml_parent(x1p)==NULL &&
|
||||
strcmp(xml_name(x1p), "config") == 0 &&
|
||||
xml_prefix(x1p)==NULL;
|
||||
if (yang_type_get(y, NULL, &yrestype,
|
||||
NULL, NULL, NULL, NULL, NULL) < 0)
|
||||
goto done;
|
||||
if (strcmp(yang_argument_get(yrestype), "identityref") != 0)
|
||||
goto ok; /* skip */
|
||||
if (nodeid_split(x1bstr, &prefix, NULL) < 0)
|
||||
goto done;
|
||||
if (prefix == NULL)
|
||||
goto ok; /* skip */
|
||||
if (xml2ns(x0, prefix, &ns0) < 0)
|
||||
goto done;
|
||||
if (xml2ns(x1, prefix, &ns1) < 0)
|
||||
goto done;
|
||||
if (ns0 != NULL){
|
||||
if (ns1){
|
||||
if (strcmp(ns0, ns1)){
|
||||
clicon_err(OE_YANG, EFAULT, "identity namespace collision: %s: %s vs %s", x1bstr, ns0, ns1);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
else{
|
||||
if (isroot)
|
||||
x = x1;
|
||||
else
|
||||
x = x1p;
|
||||
if (nscache_set(x, prefix, ns0) < 0)
|
||||
goto done;
|
||||
/* Create xmlns attribute to x1 XXX same code ^*/
|
||||
if (prefix){
|
||||
if ((xa = xml_new(prefix, x, NULL)) == NULL)
|
||||
goto done;
|
||||
if (xml_prefix_set(xa, "xmlns") < 0)
|
||||
goto done;
|
||||
}
|
||||
else{
|
||||
if ((xa = xml_new("xmlns", x, NULL)) == NULL)
|
||||
goto done;
|
||||
}
|
||||
xml_type_set(xa, CX_ATTR);
|
||||
if (xml_value_set(xa, ns0) < 0)
|
||||
goto done;
|
||||
xml_sort(x, NULL); /* Ensure attr is first / XXX xml_insert? */
|
||||
}
|
||||
}
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Modify a base tree x0 with x1 with yang spec y according to operation op
|
||||
* @param[in] th Datastore text handle
|
||||
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
|
||||
|
|
@ -208,12 +370,12 @@ text_modify(clicon_handle h,
|
|||
char *opstr = NULL;
|
||||
char *x1name;
|
||||
char *x1cname; /* child name */
|
||||
cxobj *x0a; /* attribute */
|
||||
cxobj *x1a; /* attribute */
|
||||
// cxobj *x0a; /* attribute */
|
||||
// cxobj *x1a; /* attribute */
|
||||
cxobj *x0c; /* base child */
|
||||
cxobj *x0b; /* base body */
|
||||
cxobj *x1c; /* mod child */
|
||||
char *xns; /* namespace */
|
||||
// char *xns; /* namespace */
|
||||
char *x0bstr; /* mod body string */
|
||||
char *x1bstr; /* mod body string */
|
||||
yang_stmt *yc; /* yang child */
|
||||
|
|
@ -303,7 +465,6 @@ text_modify(clicon_handle h,
|
|||
}
|
||||
} /* OP_MERGE & insert */
|
||||
case OP_NONE: /* fall thru */
|
||||
|
||||
if (x0==NULL){
|
||||
if ((op != OP_NONE) && !permit && xnacm){
|
||||
if ((ret = nacm_datanode_write(NULL, x1, NACM_CREATE, username, xnacm, cbret)) < 0)
|
||||
|
|
@ -316,32 +477,14 @@ text_modify(clicon_handle h,
|
|||
copied (see changed conditional below) */
|
||||
if ((x0 = xml_new(x1name, NULL, (yang_stmt*)y0)) == NULL)
|
||||
goto done;
|
||||
/* Get namespace from x1
|
||||
* Check if namespace exists in x0 parent
|
||||
* if not add new binding and replace in x0.
|
||||
*/
|
||||
if (check_namespaces(x1, x0, x0p) < 0)
|
||||
goto done;
|
||||
changed++;
|
||||
|
||||
/* Copy xmlns attributes ONLY, not op/insert etc */
|
||||
x1a = NULL;
|
||||
while ((x1a = xml_child_each(x1, x1a, CX_ATTR)) != NULL)
|
||||
if (strcmp(xml_name(x1a),"xmlns")==0 ||
|
||||
((xns = xml_prefix(x1a)) && strcmp(xns, "xmlns")==0)){
|
||||
#if 1 /* XXX Kludge to NOT copy RFC7950 xmlns:yang insert/key/value namespaces */
|
||||
if (strcmp(xml_value(x1a), YANG_XML_NAMESPACE)==0 ||
|
||||
strcmp(xml_value(x1a), NETCONF_BASE_NAMESPACE)==0)
|
||||
continue;
|
||||
#endif
|
||||
if ((x0a = xml_dup(x1a)) == NULL)
|
||||
goto done;
|
||||
if (xml_addsub(x0, x0a) < 0)
|
||||
goto done;
|
||||
}
|
||||
|
||||
#if 0
|
||||
/* If it is key I dont want to mark it */
|
||||
if ((iamkey=yang_key_match(yang_parent_get(y0), x1name)) < 0)
|
||||
goto done;
|
||||
if (!iamkey && op==OP_NONE)
|
||||
#else
|
||||
if (op==OP_NONE)
|
||||
#endif
|
||||
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
|
||||
if (x1bstr){ /* empty type does not have body */
|
||||
if ((x0b = xml_new("body", x0, NULL)) == NULL)
|
||||
|
|
@ -349,17 +492,9 @@ text_modify(clicon_handle h,
|
|||
xml_type_set(x0b, CX_BODY);
|
||||
}
|
||||
}
|
||||
else { /* if change existing node, replace xmlns attributes
|
||||
* This is only done for leaf/leaf-list now, eg terminals
|
||||
* and is only an embryo of checking canonical namespace
|
||||
* bindings.
|
||||
* But it does catch some cornercases where a new
|
||||
* namespace binding is replacing an old for eg identityref
|
||||
*/
|
||||
if (replace_xmlns(x0, x1) < 0)
|
||||
goto done;
|
||||
}
|
||||
if (x1bstr){
|
||||
if (check_identityref(x1, x0, x0p, x1bstr, y0) < 0)
|
||||
goto done;
|
||||
if ((x0b = xml_body_get(x0)) != NULL){
|
||||
x0bstr = xml_value(x0b);
|
||||
if (x0bstr==NULL || strcmp(x0bstr, x1bstr)){
|
||||
|
|
@ -486,6 +621,8 @@ text_modify(clicon_handle h,
|
|||
}
|
||||
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
|
||||
goto done;
|
||||
// XXX if (check_namespaces(x1, x0) < 0)
|
||||
// goto done;
|
||||
if (xml_copy(x1, x0) < 0)
|
||||
goto done;
|
||||
break;
|
||||
|
|
@ -505,21 +642,12 @@ text_modify(clicon_handle h,
|
|||
if ((x0 = xml_new(x1name, NULL, (yang_stmt*)y0)) == NULL)
|
||||
goto done;
|
||||
changed++;
|
||||
/* Copy xmlns attributes */
|
||||
x1a = NULL;
|
||||
while ((x1a = xml_child_each(x1, x1a, CX_ATTR)) != NULL)
|
||||
if (strcmp(xml_name(x1a),"xmlns")==0 ||
|
||||
((xns = xml_prefix(x1a)) && strcmp(xns, "xmlns")==0)){
|
||||
#if 1 /* XXX Kludge to NOT copy RFC7950 xmlns:yang insert/key/value namespaces */
|
||||
if (strcmp(xml_value(x1a), YANG_XML_NAMESPACE)==0 ||
|
||||
strcmp(xml_value(x1a), NETCONF_BASE_NAMESPACE)==0)
|
||||
continue;
|
||||
#endif
|
||||
if ((x0a = xml_dup(x1a)) == NULL)
|
||||
/* Get namespace from x1
|
||||
* Check if namespace exists in x0 parent
|
||||
* if not add new binding and replace in x0.
|
||||
*/
|
||||
if (check_namespaces(x1, x0, x0p) < 0)
|
||||
goto done;
|
||||
if (xml_addsub(x0, x0a) < 0)
|
||||
goto done;
|
||||
}
|
||||
if (op==OP_NONE)
|
||||
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
|
||||
}
|
||||
|
|
@ -534,23 +662,30 @@ text_modify(clicon_handle h,
|
|||
i = 0;
|
||||
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
|
||||
x1cname = xml_name(x1c);
|
||||
/* Get yang spec of the child */
|
||||
/* Get yang spec of the child by child matching */
|
||||
if ((yc = yang_find_datanode(y0, x1cname)) == NULL){
|
||||
clicon_err(OE_YANG, errno, "No yang node found: %s", x1cname);
|
||||
goto done;
|
||||
}
|
||||
/* There is a cornercase (eg augment) of multi-namespace trees where
|
||||
* the yang child has a different namespace.
|
||||
* As an alternative, return in populate where this is detected first time.
|
||||
*/
|
||||
if (yc != xml_spec(x1c)){
|
||||
clicon_err(OE_YANG, errno, "XML node %s not in namespace %s",
|
||||
x1cname, yang_find_mynamespace(y0));
|
||||
goto done;
|
||||
}
|
||||
/* See if there is a corresponding node in the base tree */
|
||||
x0c = NULL;
|
||||
if (match_base_child(x0, x1c, yc, &x0c) < 0)
|
||||
goto done;
|
||||
#if 1
|
||||
if (x0c && (yc != xml_spec(x0c))){
|
||||
/* There is a match but is should be replaced (choice)*/
|
||||
if (xml_purge(x0c) < 0)
|
||||
goto done;
|
||||
x0c = NULL;
|
||||
}
|
||||
#endif
|
||||
x0vec[i++] = x0c; /* != NULL if x0c is matching x1c */
|
||||
}
|
||||
/* Second pass: Loop through children of the x1 modification tree again
|
||||
|
|
@ -725,14 +860,12 @@ text_modify_top(clicon_handle h,
|
|||
/* See if there is a corresponding node in the base tree */
|
||||
if (match_base_child(x0, x1c, yc, &x0c) < 0)
|
||||
goto done;
|
||||
#if 1
|
||||
if (x0c && (yc != xml_spec(x0c))){
|
||||
/* There is a match but is should be replaced (choice)*/
|
||||
if (xml_purge(x0c) < 0)
|
||||
goto done;
|
||||
x0c = NULL;
|
||||
}
|
||||
#endif
|
||||
if ((ret = text_modify(h, x0c, yc, x0, x1c, op,
|
||||
username, xnacm, permit, cbret)) < 0)
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -584,7 +584,8 @@ clicon_rpc_get(clicon_handle h,
|
|||
if ((username = clicon_username_get(h)) != NULL)
|
||||
cprintf(cb, " username=\"%s\"", username);
|
||||
if (namespace)
|
||||
cprintf(cb, " xmlns:nc=\"%s\"", NETCONF_BASE_NAMESPACE);
|
||||
cprintf(cb, " xmlns:%s=\"%s\"",
|
||||
NETCONF_BASE_PREFIX, NETCONF_BASE_NAMESPACE);
|
||||
cprintf(cb, "><get");
|
||||
/* Clixon extension, content=all,config, or nonconfig */
|
||||
if (content != -1)
|
||||
|
|
@ -627,6 +628,79 @@ clicon_rpc_get(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
int
|
||||
clicon_rpc_get_nsc(clicon_handle h,
|
||||
char *xpath,
|
||||
cvec *nsc, /* namespace context for filter */
|
||||
netconf_content content,
|
||||
int32_t depth,
|
||||
cxobj **xt)
|
||||
{
|
||||
int retval = -1;
|
||||
struct clicon_msg *msg = NULL;
|
||||
cbuf *cb = NULL;
|
||||
cxobj *xret = NULL;
|
||||
cxobj *xd;
|
||||
char *username;
|
||||
cg_var *cv = NULL;
|
||||
char *prefix;
|
||||
|
||||
if ((cb = cbuf_new()) == NULL)
|
||||
goto done;
|
||||
cprintf(cb, "<rpc");
|
||||
if ((username = clicon_username_get(h)) != NULL)
|
||||
cprintf(cb, " username=\"%s\"", username);
|
||||
cprintf(cb, " xmlns:%s=\"%s\"",
|
||||
NETCONF_BASE_PREFIX, NETCONF_BASE_NAMESPACE);
|
||||
cprintf(cb, "><get");
|
||||
/* Clixon extension, content=all,config, or nonconfig */
|
||||
if (content != -1)
|
||||
cprintf(cb, " content=\"%s\"", netconf_content_int2str(content));
|
||||
/* Clixon extension, depth=<level> */
|
||||
if (depth != -1)
|
||||
cprintf(cb, " depth=\"%d\"", depth);
|
||||
cprintf(cb, ">");
|
||||
if (xpath && strlen(xpath)) {
|
||||
cprintf(cb, "<%s:filter %s:type=\"xpath\" %s:select=\"%s\"",
|
||||
NETCONF_BASE_PREFIX, NETCONF_BASE_PREFIX, NETCONF_BASE_PREFIX,
|
||||
xpath);
|
||||
|
||||
while ((cv = cvec_each(nsc, cv)) != NULL){
|
||||
cprintf(cb, " xmlns");
|
||||
if ((prefix = cv_name_get(cv)))
|
||||
cprintf(cb, ":%s", prefix);
|
||||
cprintf(cb, "=\"%s\"", cv_string_get(cv));
|
||||
}
|
||||
cprintf(cb, "/>");
|
||||
}
|
||||
cprintf(cb, "</get></rpc>");
|
||||
if ((msg = clicon_msg_encode("%s", cbuf_get(cb))) == NULL)
|
||||
goto done;
|
||||
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
|
||||
goto done;
|
||||
/* Send xml error back: first check error, then ok */
|
||||
if ((xd = xpath_first(xret, "/rpc-reply/rpc-error")) != NULL)
|
||||
xd = xml_parent(xd); /* point to rpc-reply */
|
||||
else if ((xd = xpath_first(xret, "/rpc-reply/data")) == NULL)
|
||||
if ((xd = xml_new("data", NULL, NULL)) == NULL)
|
||||
goto done;
|
||||
if (xt){
|
||||
if (xml_rm(xd) < 0)
|
||||
goto done;
|
||||
*xt = xd;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
if (xret)
|
||||
xml_free(xret);
|
||||
if (msg)
|
||||
free(msg);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/*! Close a (user) session
|
||||
* @param[in] h CLICON handle
|
||||
* @retval 0 OK
|
||||
|
|
|
|||
|
|
@ -197,9 +197,9 @@ xml_name_set(cxobj *xn,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*! Get namespace of xnode
|
||||
/*! Get prefix of xnode
|
||||
* @param[in] xn xml node
|
||||
* @retval namespace of xml node
|
||||
* @retval prefix of xml node
|
||||
*/
|
||||
char*
|
||||
xml_prefix(cxobj *xn)
|
||||
|
|
@ -207,9 +207,9 @@ xml_prefix(cxobj *xn)
|
|||
return xn->x_prefix;
|
||||
}
|
||||
|
||||
/*! Set name space of xnode, namespace is copied
|
||||
/*! Set prefix of xnode, prefix is copied
|
||||
* @param[in] xn xml node
|
||||
* @param[in] localname new namespace, null-terminated string, copied by function
|
||||
* @param[in] localname new prefix, null-terminated string, copied by function
|
||||
* @retval -1 on error with clicon-err set
|
||||
* @retval 0 OK
|
||||
*/
|
||||
|
|
@ -230,14 +230,14 @@ xml_prefix_set(cxobj *xn,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*! Get cached namespace
|
||||
/*! Get cached namespace (given prefix)
|
||||
* @param[in] x XML node
|
||||
* @param[in] prefix Namespace prefix, or NULL for default
|
||||
* @retval ns Cached namespace
|
||||
* @retval NULL No namespace found (not cached or not found)
|
||||
* @note may want to distinguish between not set cache and no namespace?
|
||||
*/
|
||||
static char*
|
||||
char*
|
||||
nscache_get(cxobj *x,
|
||||
char *prefix)
|
||||
{
|
||||
|
|
@ -246,6 +246,35 @@ nscache_get(cxobj *x,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/*! Get cached prefix (given namespace)
|
||||
* @param[in] x XML node
|
||||
* @param[in] namespace
|
||||
* @param[out] prefix
|
||||
* @retval 0 No prefix found
|
||||
* @retval 1 Prefix found
|
||||
*/
|
||||
int
|
||||
nscache_get_prefix(cxobj *x,
|
||||
char *namespace,
|
||||
char **prefix)
|
||||
{
|
||||
if (x->x_ns_cache != NULL)
|
||||
return xml_nsctx_get_prefix(x->x_ns_cache, namespace, prefix);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Dump whole namespacet context cache of one xml node
|
||||
* @param[in] x XML node
|
||||
* @retval nsc Whole namespace context of x
|
||||
* @retval NULL Empty nsc
|
||||
* @see nscache_get For a single prefix
|
||||
*/
|
||||
cvec *
|
||||
nscache_get_all(cxobj *x)
|
||||
{
|
||||
return x->x_ns_cache;
|
||||
}
|
||||
|
||||
/*! Set cached namespace for specific namespace. Replace if necessary
|
||||
* @param[in] x XML node
|
||||
* @param[in] prefix Namespace prefix, or NULL for default
|
||||
|
|
@ -254,7 +283,7 @@ nscache_get(cxobj *x,
|
|||
* @retval -1 Error
|
||||
* @see nscache_replace to replace the whole context
|
||||
*/
|
||||
static int
|
||||
int
|
||||
nscache_set(cxobj *x,
|
||||
char *prefix,
|
||||
char *namespace)
|
||||
|
|
@ -405,6 +434,63 @@ xmlns_set(cxobj *x,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Get namespace given prefix recursively
|
||||
* @param[in] xn XML node
|
||||
* @param[in] namespace Namespace
|
||||
* @param[out] prefixp Pointer to prefix if found
|
||||
* @retval -1 Error
|
||||
* @retval 0 No namespace found
|
||||
* @retval 1 Namespace found
|
||||
*/
|
||||
int
|
||||
xml2prefix(cxobj *xn,
|
||||
char *namespace,
|
||||
char **prefixp)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xa = NULL;
|
||||
char *prefix = NULL;
|
||||
|
||||
while (xn != NULL){
|
||||
if (nscache_get_prefix(xn, namespace, &prefix) == 1) /* found */
|
||||
goto found;
|
||||
// if (xn->x_ns_cache == NULL){ /* Look in node */
|
||||
xa = NULL;
|
||||
while ((xa = xml_child_each(xn, xa, CX_ATTR)) != NULL) {
|
||||
/* xmlns=namespace */
|
||||
if (strcmp("xmlns", xml_name(xa)) == 0){
|
||||
if (strcmp(xml_value(xa), namespace) == 0){
|
||||
clicon_debug(1, "%sA NULL %s", __FUNCTION__, namespace);
|
||||
if (nscache_set(xn, NULL, namespace) < 0)
|
||||
goto done;
|
||||
prefix = NULL;
|
||||
goto found;
|
||||
}
|
||||
|
||||
}
|
||||
/* xmlns:prefix=namespace */
|
||||
else if (strcmp("xmlns", xml_prefix(xa)) == 0){
|
||||
if (strcmp(xml_value(xa), namespace) == 0){
|
||||
prefix = xml_name(xa);
|
||||
assert(strcmp(prefix, "xmlns"));
|
||||
if (nscache_set(xn, prefix, namespace) < 0)
|
||||
goto done;
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
xn = xml_parent(xn);
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
found:
|
||||
*prefixp = prefix;
|
||||
retval = 1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! See if xmlns:[<localname>=]<uri> exists, if so return <uri>
|
||||
*
|
||||
* @param[in] xn XML node
|
||||
|
|
@ -982,6 +1068,7 @@ int
|
|||
xml_addsub(cxobj *xp,
|
||||
cxobj *xc)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *oldp;
|
||||
int i;
|
||||
char *pns = NULL; /* parent namespace */
|
||||
|
|
@ -1000,13 +1087,14 @@ xml_addsub(cxobj *xp,
|
|||
/* Add xc to new parent */
|
||||
if (xp){
|
||||
if (xml_child_append(xp, xc) < 0)
|
||||
return -1;
|
||||
goto done;
|
||||
/* Set new parent in child */
|
||||
xml_parent_set(xc, xp);
|
||||
/* Ensure default namespace is not duplicated
|
||||
* here only remove duplicate default namespace, there may be more */
|
||||
/* 1. Get parent default namespace */
|
||||
xml2ns(xp, NULL, &pns);
|
||||
if (xml2ns(xp, NULL, &pns) < 0)
|
||||
goto done;
|
||||
/* 2. Get child default namespace */
|
||||
if (pns &&
|
||||
(xa = xml_find_type(xc, NULL, "xmlns", CX_ATTR)) != NULL &&
|
||||
|
|
@ -1016,9 +1104,11 @@ xml_addsub(cxobj *xp,
|
|||
xml_purge(xa);
|
||||
}
|
||||
/* clear namespace context cache of child */
|
||||
nscache_clear(xp);
|
||||
nscache_clear(xc);
|
||||
}
|
||||
return 0;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Wrap a new node between a parent xml node (xp) and all its children
|
||||
|
|
|
|||
|
|
@ -1749,54 +1749,59 @@ yang2api_path_fmt_1(yang_stmt *ys,
|
|||
cbuf *cb)
|
||||
{
|
||||
yang_stmt *yp; /* parent */
|
||||
yang_stmt *ymod;
|
||||
int i;
|
||||
cvec *cvk = NULL; /* vector of index keys */
|
||||
int retval = -1;
|
||||
|
||||
if ((yp = ys->ys_parent) == NULL){
|
||||
clicon_err(OE_YANG, EINVAL, "yang expected parent %s", ys->ys_argument);
|
||||
if ((yp = yang_parent_get(ys)) == NULL){
|
||||
clicon_err(OE_YANG, EINVAL, "yang expected parent %s", yang_argument_get(ys));
|
||||
goto done;
|
||||
}
|
||||
if (yp != NULL && /* XXX rm */
|
||||
yp->ys_keyword != Y_MODULE &&
|
||||
yp->ys_keyword != Y_SUBMODULE){
|
||||
yang_keyword_get(yp) != Y_MODULE &&
|
||||
yang_keyword_get(yp) != Y_SUBMODULE){
|
||||
|
||||
if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) /* recursive call */
|
||||
goto done;
|
||||
if (yp->ys_keyword != Y_CHOICE && yp->ys_keyword != Y_CASE){
|
||||
if (yang_keyword_get(yp) != Y_CHOICE && yang_keyword_get(yp) != Y_CASE){
|
||||
#if 0
|
||||
/* In some cases, such as cli_show_auto, a trailing '/' should
|
||||
* NOT be present if ys is a key in a list.
|
||||
* But in other cases (I think most), the / should be there,
|
||||
* so a patch is added in cli_show_auto instead.
|
||||
*/
|
||||
if (ys->ys_keyword == Y_LEAF && yp &&
|
||||
yp->ys_keyword == Y_LIST &&
|
||||
if (yang_keyword_get(ys) == Y_LEAF && yp &&
|
||||
yang_keyword_get(yp) == Y_LIST &&
|
||||
yang_key_match(yp, ys->ys_argument) == 1)
|
||||
;
|
||||
else
|
||||
#endif
|
||||
cprintf(cb, "/");
|
||||
}
|
||||
/* If parent namespace/module is different from child -> add child prefix */
|
||||
if (ys_real_module(yp) != (ymod = ys_real_module(ys)))
|
||||
cprintf(cb, "%s:", yang_argument_get(ymod));
|
||||
}
|
||||
else /* top symbol - mark with name prefix */
|
||||
cprintf(cb, "/%s:", yp->ys_argument);
|
||||
cprintf(cb, "/%s:", yang_argument_get(yp));
|
||||
|
||||
if (inclkey){
|
||||
if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE)
|
||||
cprintf(cb, "%s", ys->ys_argument);
|
||||
if (yang_keyword_get(ys) != Y_CHOICE && yang_keyword_get(ys) != Y_CASE)
|
||||
cprintf(cb, "%s", yang_argument_get(ys));
|
||||
}
|
||||
else{
|
||||
if (ys->ys_keyword == Y_LEAF && yp &&
|
||||
yp->ys_keyword == Y_LIST){
|
||||
if (yang_key_match(yp, ys->ys_argument) == 0)
|
||||
cprintf(cb, "%s", ys->ys_argument); /* Not if leaf and key */
|
||||
if (yang_keyword_get(ys) == Y_LEAF && yp &&
|
||||
yang_keyword_get(yp) == Y_LIST){
|
||||
if (yang_key_match(yp, yang_argument_get(ys)) == 0)
|
||||
cprintf(cb, "%s", yang_argument_get(ys)); /* Not if leaf and key */
|
||||
}
|
||||
else
|
||||
if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE)
|
||||
cprintf(cb, "%s", ys->ys_argument);
|
||||
if (yang_keyword_get(ys) != Y_CHOICE && yang_keyword_get(ys) != Y_CASE)
|
||||
cprintf(cb, "%s", yang_argument_get(ys));
|
||||
}
|
||||
|
||||
switch (ys->ys_keyword){
|
||||
switch (yang_keyword_get(ys)){
|
||||
case Y_LIST:
|
||||
cvk = ys->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
|
||||
if (cvec_len(cvk))
|
||||
|
|
@ -2168,6 +2173,8 @@ xml_default(cxobj *xt,
|
|||
cxobj *xb;
|
||||
char *str;
|
||||
int added=0;
|
||||
char *namespace;
|
||||
char *prefix;
|
||||
|
||||
if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){
|
||||
retval = 0;
|
||||
|
|
@ -2183,9 +2190,16 @@ xml_default(cxobj *xt,
|
|||
assert(y->ys_cv);
|
||||
if (!cv_flag(y->ys_cv, V_UNSET)){ /* Default value exists */
|
||||
if (!xml_find(xt, y->ys_argument)){
|
||||
|
||||
if ((xc = xml_new(y->ys_argument, NULL, y)) == NULL)
|
||||
goto done;
|
||||
/* assign right prefix */
|
||||
if ((namespace = yang_find_mynamespace(y)) != NULL){
|
||||
prefix = NULL;
|
||||
if (xml2prefix(xt, namespace, &prefix))
|
||||
if (xml_prefix_set(xc, prefix) < 0)
|
||||
goto done;
|
||||
}
|
||||
|
||||
xml_flag_set(xc, XML_FLAG_DEFAULT);
|
||||
if ((xb = xml_new("body", xc, NULL)) == NULL)
|
||||
goto done;
|
||||
|
|
@ -2336,6 +2350,8 @@ xml_spec_populate(cxobj *x,
|
|||
yang_stmt *ymod; /* yang module */
|
||||
cxobj *xp = NULL; /* xml parent */
|
||||
char *name;
|
||||
char *ns = NULL; /* XML namespace of x */
|
||||
char *nsy = NULL; /* Yang namespace of x */
|
||||
|
||||
yspec = (yang_stmt*)arg;
|
||||
xp = xml_parent(x);
|
||||
|
|
@ -2343,8 +2359,11 @@ xml_spec_populate(cxobj *x,
|
|||
#ifdef DEBUG
|
||||
clicon_debug(1, "%s name:%s", __FUNCTION__, name);
|
||||
#endif
|
||||
if (xp && (yparent = xml_spec(xp)) != NULL)
|
||||
if (xml2ns(x, xml_prefix(x), &ns) < 0)
|
||||
goto done;
|
||||
if (xp && (yparent = xml_spec(xp)) != NULL){
|
||||
y = yang_find_datanode(yparent, name);
|
||||
}
|
||||
else if (yspec){
|
||||
if (ys_module_by_xml(yspec, x, &ymod) < 0)
|
||||
goto done;
|
||||
|
|
@ -2361,9 +2380,16 @@ xml_spec_populate(cxobj *x,
|
|||
#endif
|
||||
}
|
||||
if (y) {
|
||||
nsy = yang_find_mynamespace(y);
|
||||
if (ns == NULL || nsy == NULL){
|
||||
clicon_err(OE_XML, EFAULT, "Namespace NULL");
|
||||
goto done;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
clicon_debug(1, "%s y:%s", __FUNCTION__, yang_argument_get(y));
|
||||
#endif
|
||||
/* Assign spec only if namespaces match */
|
||||
if (strcmp(ns, nsy) == 0)
|
||||
xml_spec_set(x, y);
|
||||
}
|
||||
#ifdef DEBUG
|
||||
|
|
@ -2387,13 +2413,7 @@ xml_spec_populate(cxobj *x,
|
|||
* @retval 0 Invalid api_path or associated XML, netconf error xml set
|
||||
* @retval -1 Fatal error, clicon_err called
|
||||
*
|
||||
* @note both retval -1 set clicon_err, retval 0 sets netconf xml msg
|
||||
* @note Not proper namespace translation from api-path 2 xpath
|
||||
* It works like this:
|
||||
* Assume origin incoming path is
|
||||
* "www.foo.com/restconf/a/b=c", pi is 2 and pcvec is:
|
||||
* ["www.foo.com" "restconf" "a" "b=c"]
|
||||
* which means the api-path is ["a" "b=c"] corresponding to "a/b=c"
|
||||
|
||||
* @code
|
||||
* cbuf *xpath = cbuf_new();
|
||||
* cvec *cvv = NULL;
|
||||
|
|
@ -2405,7 +2425,14 @@ xml_spec_populate(cxobj *x,
|
|||
* ... access xpath as cbuf_get(xpath)
|
||||
* cbuf_free(xpath)
|
||||
* @endcode
|
||||
* It works like this:
|
||||
* Assume origin incoming path is
|
||||
* "www.foo.com/restconf/a/b=c", pi is 2 and pcvec is:
|
||||
* ["www.foo.com" "restconf" "a" "b=c"]
|
||||
* which means the api-path is ["a" "b=c"] corresponding to "a/b=c"
|
||||
* @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
|
||||
* @note retval -1 sets clicon_err, retval 0 sets netconf xml msg
|
||||
* @note Not proper namespace translation from api-path 2 xpath!!!
|
||||
* @see api_path2xml For api-path to xml tree
|
||||
* @see api_path2xpath Using strings as parameters
|
||||
*/
|
||||
|
|
@ -2438,7 +2465,8 @@ api_path2xpath_cvv(cvec *api_path,
|
|||
nodeid = cv_name_get(cv);
|
||||
if (nodeid_split(nodeid, &prefix, &name) < 0)
|
||||
goto done;
|
||||
clicon_debug(1, "%s [%d] cvname: %s:%s", __FUNCTION__, i, prefix?prefix:"", name);
|
||||
clicon_debug(1, "%s [%d] cvname: %s:%s",
|
||||
__FUNCTION__, i, prefix?prefix:"", name);
|
||||
if (i == offset){ /* top-node */
|
||||
if (prefix == NULL){
|
||||
if ((cberr = cbuf_new()) == NULL){
|
||||
|
|
@ -2536,6 +2564,170 @@ api_path2xpath_cvv(cvec *api_path,
|
|||
goto done;
|
||||
}
|
||||
|
||||
/*! Temp alternative to api_path2xpath_cvv */
|
||||
int
|
||||
api_path2xpath_cvv2(cvec *api_path,
|
||||
int offset,
|
||||
yang_stmt *yspec,
|
||||
cbuf *xpath,
|
||||
cvec *nsc,
|
||||
cxobj **xerr)
|
||||
{
|
||||
int retval = -1;
|
||||
int i;
|
||||
cg_var *cv;
|
||||
char *nodeid;
|
||||
char *prefix = NULL; /* api-path (module) prefix */
|
||||
char *xprefix = NULL; /* xml xpath prefix */
|
||||
char *name = NULL;
|
||||
cvec *cvk = NULL; /* vector of index keys */
|
||||
yang_stmt *y = NULL;
|
||||
yang_stmt *ymod = NULL;
|
||||
char *val;
|
||||
cg_var *cvi;
|
||||
char **valvec = NULL;
|
||||
int vi;
|
||||
int nvalvec;
|
||||
cbuf *cberr = NULL;
|
||||
char *namespace = NULL;
|
||||
|
||||
for (i=offset; i<cvec_len(api_path); i++){
|
||||
cv = cvec_i(api_path, i);
|
||||
nodeid = cv_name_get(cv);
|
||||
/* api-path: prefix points to module */
|
||||
if (nodeid_split(nodeid, &prefix, &name) < 0)
|
||||
goto done;
|
||||
clicon_debug(1, "%s [%d] cvname: %s:%s",
|
||||
__FUNCTION__, i, prefix?prefix:"", name);
|
||||
/* top-node must have prefix */
|
||||
if (i == offset && prefix == NULL){
|
||||
if ((cberr = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
cprintf(cberr, "'%s': Expected prefix:name", nodeid);
|
||||
if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
ymod = NULL;
|
||||
if (prefix){ /* if prefix -> get module + change namespace */
|
||||
if ((ymod = yang_find_module_by_name(yspec, prefix)) == NULL){
|
||||
if ((cberr = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
cprintf(cberr, "No such yang module: %s", prefix);
|
||||
if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
namespace = yang_find_mynamespace(ymod); /* change namespace */
|
||||
}
|
||||
if (i == offset && ymod) /* root */
|
||||
y = yang_find_datanode(ymod, name);
|
||||
else
|
||||
y = yang_find_datanode(y, name);
|
||||
if (y == NULL){
|
||||
if (netconf_unknown_element_xml(xerr, "application", name, "Unknown element") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
/* Get XML/xpath prefix given namespace.
|
||||
* note different from api-path prefix
|
||||
*/
|
||||
if (xml_nsctx_get_prefix(nsc, namespace, &xprefix) == 0){
|
||||
|
||||
xprefix = yang_find_myprefix(y);
|
||||
clicon_debug(1, "%s prefix not found add it %s", __FUNCTION__, xprefix);
|
||||
/* not found, add it to nsc */
|
||||
if (xml_nsctx_set(nsc, xprefix, namespace) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* Check if has value, means '=' */
|
||||
if (cv2str(cv, NULL, 0) > 0){
|
||||
if ((val = cv2str_dup(cv)) == NULL)
|
||||
goto done;
|
||||
switch (yang_keyword_get(y)){
|
||||
case Y_LIST:
|
||||
/* Transform value "a,b,c" to "a" "b" "c" (nvalvec=3)
|
||||
* Note that vnr can be < length of cvk, due to empty or unset values
|
||||
*/
|
||||
if (valvec){ /* loop, valvec may have been used before */
|
||||
free(valvec);
|
||||
valvec = NULL;
|
||||
}
|
||||
if ((valvec = clicon_strsep(val, ",", &nvalvec)) == NULL)
|
||||
goto done;
|
||||
cvk = y->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
|
||||
cvi = NULL;
|
||||
/* Iterate over individual yang keys */
|
||||
cprintf(xpath, "/");
|
||||
if (xprefix)
|
||||
cprintf(xpath, "%s:", xprefix);
|
||||
cprintf(xpath, "%s", name);
|
||||
vi = 0;
|
||||
while ((cvi = cvec_each(cvk, cvi)) != NULL && vi<nvalvec){
|
||||
cprintf(xpath, "[");
|
||||
if (xprefix)
|
||||
cprintf(xpath, "%s:", xprefix);
|
||||
cprintf(xpath, "%s='%s']", cv_string_get(cvi), valvec[vi++]);
|
||||
}
|
||||
break;
|
||||
case Y_LEAF_LIST: /* XXX: LOOP? */
|
||||
cprintf(xpath, "/");
|
||||
if (xprefix)
|
||||
cprintf(xpath, "%s:", xprefix);
|
||||
cprintf(xpath, "%s", name);
|
||||
if (val)
|
||||
cprintf(xpath, "[.='%s']", val);
|
||||
else
|
||||
cprintf(xpath, "[.='']");
|
||||
break;
|
||||
default:
|
||||
if (i != offset)
|
||||
cprintf(xpath, "/");
|
||||
if (xprefix)
|
||||
cprintf(xpath, "%s:", xprefix);
|
||||
cprintf(xpath, "%s", name);
|
||||
break;
|
||||
}
|
||||
if (val)
|
||||
free(val);
|
||||
}
|
||||
else{
|
||||
if (i != offset)
|
||||
cprintf(xpath, "/");
|
||||
if (xprefix)
|
||||
cprintf(xpath, "%s:", xprefix);
|
||||
cprintf(xpath, "%s", name);
|
||||
}
|
||||
if (prefix){
|
||||
free(prefix);
|
||||
prefix = NULL;
|
||||
}
|
||||
if (name){
|
||||
free(name);
|
||||
name = NULL;
|
||||
}
|
||||
} /* for */
|
||||
retval = 1; /* OK */
|
||||
done:
|
||||
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
||||
if (cberr != NULL)
|
||||
cbuf_free(cberr);
|
||||
if (valvec)
|
||||
free(valvec);
|
||||
if (prefix)
|
||||
free(prefix);
|
||||
if (name)
|
||||
free(name);
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0; /* Validation failed */
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! Translate from restconf api-path to xml xpath and namespace
|
||||
* @param[in] api_path URI-encoded path expression" (RFC8040 3.5.3)
|
||||
* @param[in] yspec Yang spec
|
||||
|
|
@ -2674,7 +2866,6 @@ api_path2xml_vec(char **vec,
|
|||
}
|
||||
namespace = yang_find_mynamespace(ymod);
|
||||
y0 = ymod;
|
||||
|
||||
}
|
||||
y = (nodeclass==YC_SCHEMANODE)?
|
||||
yang_find_schemanode(y0, name):
|
||||
|
|
@ -2683,6 +2874,13 @@ api_path2xml_vec(char **vec,
|
|||
clicon_err(OE_YANG, EINVAL, "api-path name: '%s', no such yang element", name);
|
||||
goto fail;
|
||||
}
|
||||
if (prefix && namespace == NULL){
|
||||
if ((ymod = yang_find_module_by_name(ys_spec(y0), prefix)) == NULL){
|
||||
clicon_err(OE_YANG, EINVAL, "api-path element prefix: '%s', no such yang module", prefix);
|
||||
goto fail;
|
||||
}
|
||||
namespace = yang_find_mynamespace(ymod);
|
||||
}
|
||||
switch (y->ys_keyword){
|
||||
case Y_LEAF_LIST:
|
||||
if (0 && restval==NULL){
|
||||
|
|
|
|||
|
|
@ -338,7 +338,6 @@ xml_cmp_qsort(const void* arg1,
|
|||
return xml_cmp(*(struct xml**)arg1, *(struct xml**)arg2, 1);
|
||||
}
|
||||
|
||||
|
||||
/*! Sort children of an XML node
|
||||
* Assume populated by yang spec.
|
||||
* @param[in] x0 XML node
|
||||
|
|
@ -615,6 +614,10 @@ xml_insert2(cxobj *xp,
|
|||
}
|
||||
xc = xml_child_i(xp, mid);
|
||||
if ((yc = xml_spec(xc)) == NULL){
|
||||
if (xml_type(xc) != CX_ELMNT)
|
||||
clicon_err(OE_XML, 0, "No spec found %s (wrong xml type:%d)",
|
||||
xml_name(xc), xml_type(xc));
|
||||
else
|
||||
clicon_err(OE_XML, 0, "No spec found %s", xml_name(xc));
|
||||
goto done;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,12 @@
|
|||
# both defined in the basic ietf-interfaces module (type) as well as the main
|
||||
# module through the augmented module ()
|
||||
# The ietf-interfaces is very restricted (not original).
|
||||
|
||||
# From a namespace perspective, there are two modules, with symbols as follows:
|
||||
# 1. ietf-interface - urn:ietf:params:xml:ns:yang:ietf-interfaces
|
||||
# interfaces, interface, name, type
|
||||
# 2. example-augment - urn:example:augment - mymod
|
||||
# (augmented): mandatory-leaf, me, other,
|
||||
# (uses/grouping): ip, port, lid, lport
|
||||
# Magic line must be first in script (see README.md)
|
||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
||||
|
|
@ -175,7 +180,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></
|
|||
</interface></interfaces></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf verify get with refined ports"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface xmlns:mymod="urn:example:augment"><name>e1</name><type>mymod:some-new-iftype</type><mandatory-leaf>true</mandatory-leaf><port>80</port><lport>8080</lport></interface></interfaces></data></rpc-reply>]]>]]>$'
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface xmlns:mymod="urn:example:augment"><name>e1</name><type>mymod:some-new-iftype</type><mymod:mandatory-leaf>true</mymod:mandatory-leaf><mymod:port>80</mymod:port><mymod:lport>8080</mymod:lport></interface></interfaces></data></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf set identity defined in other"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
|
||||
|
|
@ -183,7 +188,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></
|
|||
<name>e2</name>
|
||||
<type>fddi</type>
|
||||
<mymod:mandatory-leaf>true</mymod:mandatory-leaf>
|
||||
<other>if:fddi</other>
|
||||
<mymod:other>if:fddi</mymod:other>
|
||||
</interface></interfaces></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf validate ok"
|
||||
|
|
@ -195,7 +200,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></
|
|||
<name>e3</name>
|
||||
<type>fddi</type>
|
||||
<mymod:mandatory-leaf>true</mymod:mandatory-leaf>
|
||||
<me>mymod:you</me>
|
||||
<mymod:me>mymod:you</mymod:me>
|
||||
</interface></interfaces></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf commit ok"
|
||||
|
|
@ -203,11 +208,11 @@ expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" '^<rpc-reply
|
|||
|
||||
# restconf and augment
|
||||
|
||||
new "restconf get augment"
|
||||
new "restconf get augment json"
|
||||
expectpart "$(curl -s -i -X GET http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK
' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"e1","type":"example-augment:some-new-iftype","example-augment:mandatory-leaf":"true","example-augment:port":80,"example-augment:lport":8080},{"name":"e2","type":"fddi","example-augment:mandatory-leaf":"true","example-augment:other":"ietf-interfaces:fddi","example-augment:port":80,"example-augment:lport":8080},{"name":"e3","type":"fddi","example-augment:mandatory-leaf":"true","example-augment:me":"you","example-augment:port":80,"example-augment:lport":8080}\]}}
'
|
||||
|
||||
new "restconf get augment xml"
|
||||
expectpart "$(curl -s -i -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK
' '<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface xmlns:mymod="urn:example:augment"><name>e1</name><type>mymod:some-new-iftype</type><mandatory-leaf>true</mandatory-leaf><port>80</port><lport>8080</lport></interface><interface xmlns:mymod="urn:example:augment"><name>e2</name><type>fddi</type><mandatory-leaf>true</mandatory-leaf><other>if:fddi</other><port>80</port><lport>8080</lport></interface><interface xmlns:mymod="urn:example:augment"><name>e3</name><type>fddi</type><mandatory-leaf>true</mandatory-leaf><me>mymod:you</me><port>80</port><lport>8080</lport></interface></interfaces>'
|
||||
expectpart "$(curl -s -i -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK
' '<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface xmlns:mymod="urn:example:augment"><name>e1</name><type>mymod:some-new-iftype</type><mymod:mandatory-leaf>true</mymod:mandatory-leaf><mymod:port>80</mymod:port><mymod:lport>8080</mymod:lport></interface><interface xmlns:mymod="urn:example:augment"><name>e2</name><type>fddi</type><mymod:mandatory-leaf>true</mymod:mandatory-leaf><mymod:other>if:fddi</mymod:other><mymod:port>80</mymod:port><mymod:lport>8080</mymod:lport></interface><interface xmlns:mymod="urn:example:augment"><name>e3</name><type>fddi</type><mymod:mandatory-leaf>true</mymod:mandatory-leaf><mymod:me>mymod:you</mymod:me><mymod:port>80</mymod:port><mymod:lport>8080</mymod:lport></interface></interfaces>'
|
||||
|
||||
new "Kill restconf daemon"
|
||||
stop_restconf
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ expectfn "$clixon_cli -1 -f $cfg -l o validate" 255 "Validate failed. Edit and t
|
|||
|
||||
new "cli configure ip addr"
|
||||
expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 ipv4 address 1.2.3.4 prefix-length 24" 0 "^$"
|
||||
|
||||
new "cli configure ip descr"
|
||||
expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 description mydesc" 0 "^$"
|
||||
new "cli configure ip type"
|
||||
|
|
|
|||
|
|
@ -56,10 +56,10 @@ module example{
|
|||
}
|
||||
}
|
||||
leaf address {
|
||||
description "From RFC7950 9.9.5";
|
||||
description "From RFC7950 9.9.6";
|
||||
type leafref {
|
||||
path "../../if:interfaces/if:interface[if:name = current()/../relname]"
|
||||
+ "/if:ipv4/if:address/if:ip";
|
||||
+ "/ip:ipv4/ip:address/ip:ip";
|
||||
}
|
||||
}
|
||||
leaf wrong {
|
||||
|
|
@ -85,29 +85,29 @@ EOF
|
|||
|
||||
BASEXML=$(cat <<EOF
|
||||
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
|
||||
<interface>
|
||||
<interface xmlns:ip="urn:ietf:params:xml:ns:yang:ietf-ip">
|
||||
<name>eth0</name>
|
||||
<type>ex:eth</type>
|
||||
<ipv4>
|
||||
<address>
|
||||
<ip>192.0.2.1</ip>
|
||||
<prefix-length>24</prefix-length>
|
||||
</address>
|
||||
<address>
|
||||
<ip>192.0.2.2</ip>
|
||||
<prefix-length>24</prefix-length>
|
||||
</address>
|
||||
</ipv4>
|
||||
<ip:ipv4>
|
||||
<ip:address>
|
||||
<ip:ip>192.0.2.1</ip:ip>
|
||||
<ip:prefix-length>24</ip:prefix-length>
|
||||
</ip:address>
|
||||
<ip:address>
|
||||
<ip:ip>192.0.2.2</ip:ip>
|
||||
<ip:prefix-length>24</ip:prefix-length>
|
||||
</ip:address>
|
||||
</ip:ipv4>
|
||||
</interface>
|
||||
<interface>
|
||||
<interface xmlns:ip="urn:ietf:params:xml:ns:yang:ietf-ip">
|
||||
<name>lo</name>
|
||||
<type>ex:lo</type>
|
||||
<ipv4>
|
||||
<address>
|
||||
<ip>127.0.0.1</ip>
|
||||
<prefix-length>32</prefix-length>
|
||||
</address>
|
||||
</ipv4>
|
||||
<ip:ipv4>
|
||||
<ip:address>
|
||||
<ip:ip>127.0.0.1</ip:ip>
|
||||
<ip:prefix-length>32</ip:prefix-length>
|
||||
</ip:address>
|
||||
</ip:ipv4>
|
||||
</interface>
|
||||
</interfaces>
|
||||
EOF
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ new "netconf discard-changes"
|
|||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf edit config"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth/0/0</name></interface><interface><name>eth1</name><enabled>true</enabled><ipv4><address><ip>9.2.3.4</ip><prefix-length>24</prefix-length></address></ipv4></interface></interfaces></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth/0/0</name></interface><interface xmlns:ip="urn:ietf:params:xml:ns:yang:ietf-ip"><name>eth1</name><enabled>true</enabled><ip:ipv4><ip:address><ip:ip>9.2.3.4</ip:ip><ip:prefix-length>24</ip:prefix-length></ip:address></ip:ipv4></interface></interfaces></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
# Too many quotes
|
||||
cat <<EOF > $tmp # new
|
||||
|
|
@ -102,7 +102,7 @@ cat <<EOF > $tmp # new
|
|||
EOF
|
||||
|
||||
new "netconf get config xpath"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "$(cat $tmp)" '^<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth1</name><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>$'
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "$(cat $tmp)" '^<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface xmlns:ip="urn:ietf:params:xml:ns:yang:ietf-ip"><name>eth1</name><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>$'
|
||||
|
||||
# Too many quotes
|
||||
cat <<EOF > $tmp # new
|
||||
|
|
@ -110,7 +110,7 @@ cat <<EOF > $tmp # new
|
|||
EOF
|
||||
|
||||
new "netconf get config xpath parent"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "$(cat $tmp)" '^<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth/0/0</name><enabled>true</enabled></interface><interface><name>eth1</name><enabled>true</enabled><ipv4><enabled>true</enabled><forwarding>false</forwarding><address><ip>9.2.3.4</ip><prefix-length>24</prefix-length></address></ipv4></interface></interfaces></data></rpc-reply>]]>]]>$'
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "$(cat $tmp)" '^<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth/0/0</name><enabled>true</enabled></interface><interface xmlns:ip="urn:ietf:params:xml:ns:yang:ietf-ip"><name>eth1</name><enabled>true</enabled><ip:ipv4><ip:enabled>true</ip:enabled><ip:forwarding>false</ip:forwarding><ip:address><ip:ip>9.2.3.4</ip:ip><ip:prefix-length>24</ip:prefix-length></ip:address></ip:ipv4></interface></interfaces></data></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf validate missing type"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error>"
|
||||
|
|
|
|||
|
|
@ -11,7 +11,10 @@
|
|||
# usually contain a payload that represents the error condition, such
|
||||
# that it describes the error state and what next steps are suggested
|
||||
# for resolving it.
|
||||
|
||||
#
|
||||
# Note this is different from an api-path that is invalid from a yang point
|
||||
# of view, this is interpreted as 400 Bad Request invalid-value/unknown-element
|
||||
# XXX: complete non-existent yang with unknown-element for all PUT/POST/GET api-paths
|
||||
|
||||
# Magic line must be first in script (see README.md)
|
||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
|
@ -20,7 +23,7 @@ APPNAME=example
|
|||
|
||||
cfg=$dir/conf.xml
|
||||
fyang=$dir/example.yang
|
||||
fyang2=$dir/aug.yang
|
||||
fyang2=$dir/augment.yang
|
||||
fxml=$dir/initial.xml
|
||||
|
||||
# <CLICON_YANG_MODULE_MAIN>example</CLICON_YANG_MODULE_MAIN>
|
||||
|
|
@ -34,15 +37,24 @@ cat <<EOF > $cfg
|
|||
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
|
||||
<CLICON_BACKEND_PIDFILE>$dir/restconf.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
|
||||
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
|
||||
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
cat <<EOF > $fyang2
|
||||
module augm{
|
||||
module augment{
|
||||
yang-version 1.1;
|
||||
namespace "urn:example:aug";
|
||||
prefix aug;
|
||||
description "Used as a base for augment";
|
||||
container route-config {
|
||||
description
|
||||
"Root container for routing models";
|
||||
container dynamic {
|
||||
}
|
||||
}
|
||||
container route-state {
|
||||
description
|
||||
"Root container for routing models";
|
||||
|
|
@ -58,7 +70,7 @@ module example{
|
|||
yang-version 1.1;
|
||||
namespace "urn:example:clixon";
|
||||
prefix ex;
|
||||
import aug {
|
||||
import augment {
|
||||
description "Just for augment";
|
||||
prefix "aug";
|
||||
}
|
||||
|
|
@ -86,13 +98,16 @@ module example{
|
|||
}
|
||||
augment "/aug:route-config/aug:dynamic" {
|
||||
container ospf {
|
||||
container routers {
|
||||
container auto-cost {
|
||||
leaf reference-bandwidth {
|
||||
type uint32;
|
||||
}
|
||||
}
|
||||
}
|
||||
augment "/aug:route-config/aug:dynamic" {
|
||||
container ospf {
|
||||
leaf reference-bandwidth {
|
||||
type uint32;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -147,13 +162,28 @@ if false; then
|
|||
new "restconf POST non-existent (no yang) element"
|
||||
# should be invalid element
|
||||
expectpart "$(curl -is -X POST -H 'Content-Type: application/yang-data+xml' -d "$XML" http://localhost/restconf/data/example:a=23/xxx)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Unknown element: '
|
||||
|
||||
|
||||
new "restconf GET multi-namespace path"
|
||||
# simplify yang
|
||||
# works for config?
|
||||
expectpart "$(curl -si -X GET http://localhost/restconf/data/augm:route-state/dynamic/ospf/routers/auto-cost/reference-bandwidth)" 0 'HTTP/1.1 404 Not Found' '{"ietf-restconf:errors":{"error":{"rpc-error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Unknown element: 'xxx'"}}}}'
|
||||
fi
|
||||
|
||||
# Test for multi-module path where an augment stretches across modules
|
||||
new "restconf POST augment multi-namespace path"
|
||||
expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' -d '<route-config xmlns="urn:example:aug"><dynamic><ospf xmlns="urn:example:clixon"><reference-bandwidth>23</reference-bandwidth></ospf></dynamic></route-config>' http://localhost/restconf/data)" 0 ''
|
||||
|
||||
new "restconf GET augment multi-namespace top"
|
||||
expectpart "$(curl -si -X GET http://localhost/restconf/data/augment:route-config)" 0 'HTTP/1.1 200 OK' '{"augment:route-config":{"dynamic":{"example:ospf":{"reference-bandwidth":23}}}}'
|
||||
|
||||
new "restconf GET augment multi-namespace level 1"
|
||||
expectpart "$(curl -si -X GET http://localhost/restconf/data/augment:route-config/dynamic)" 0 'HTTP/1.1 200 OK' '{"augment:dynamic":{"example:ospf":{"reference-bandwidth":23}}}'
|
||||
|
||||
new "restconf GET augment multi-namespace cross"
|
||||
expectpart "$(curl -si -X GET http://localhost/restconf/data/augment:route-config/dynamic/example:ospf)" 0 'HTTP/1.1 200 OK' '{"example:ospf":{"reference-bandwidth":23}}'
|
||||
|
||||
new "restconf GET augment multi-namespace cross level 2"
|
||||
expectpart "$(curl -si -X GET http://localhost/restconf/data/augment:route-config/dynamic/example:ospf/reference-bandwidth)" 0 'HTTP/1.1 200 OK' '{"example:reference-bandwidth":23}'
|
||||
|
||||
# XXX actually no such element
|
||||
#new "restconf GET augment multi-namespace, no 2nd module in api-path, fail"
|
||||
#expectpart "$(curl -si -X GET http://localhost/restconf/data/augment:route-config/dynamic/ospf)" 0 'HTTP/1.1 404 Not Found' '{"ietf-restconf:errors":{"error":{"rpc-error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}}'
|
||||
|
||||
new "Kill restconf daemon"
|
||||
stop_restconf
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue