First working prototype
This commit is contained in:
parent
6b357dc038
commit
2e857bb417
16 changed files with 968 additions and 28 deletions
|
|
@ -533,6 +533,14 @@ Developers may need to change their code
|
|||
* See [namespace prefix nc is not supported in full #154](https://github.com/clicon/clixon/issues/154)
|
||||
* Fixed [Clixon backend generates wrong XML on empty string value #144](https://github.com/clicon/clixon/issues/144)
|
||||
|
||||
### New features
|
||||
|
||||
* Prototype of collection draft
|
||||
* This is prototype work for ietf netconf work
|
||||
* See draft-ietf-netconf-restconf-collection-00.txt
|
||||
* New yang: ietf-restconf-collection@2020-10-22.yang
|
||||
* New http media: application/yang.collection+xml/json
|
||||
|
||||
## 4.8.0
|
||||
18 October 2020
|
||||
|
||||
|
|
|
|||
|
|
@ -1335,6 +1335,305 @@ from_client_kill_session(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Help function for parsing restconf query parameter and setting netconf attribute
|
||||
*
|
||||
* If not "unbounded", parse and set a numeric value
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] name Name of attribute
|
||||
* @param[in,out] cbret Output buffer for internal RPC message
|
||||
* @param[out] value Value
|
||||
* @retval -1 Error
|
||||
* @retval 0 Invalid, cbret set
|
||||
* @retval 1 OK
|
||||
*/
|
||||
static int
|
||||
element2value(clicon_handle h,
|
||||
cxobj *xe,
|
||||
char *name,
|
||||
cbuf *cbret,
|
||||
uint32_t *value)
|
||||
{
|
||||
int retval = -1;
|
||||
char *valstr;
|
||||
int ret;
|
||||
char *reason = NULL;
|
||||
cxobj *x;
|
||||
|
||||
*value = 0;
|
||||
if ((x = xml_find_type(xe, NULL, name, CX_ELMNT)) != NULL &&
|
||||
(valstr = xml_body(x)) != NULL &&
|
||||
strcmp(valstr, "unbounded") != 0){
|
||||
if ((ret = parse_uint32(valstr, value, &reason)) < 0){
|
||||
clicon_err(OE_XML, errno, "parse_uint32");
|
||||
goto done;
|
||||
}
|
||||
if (ret == 0){
|
||||
if (netconf_bad_attribute(cbret, "application",
|
||||
"count", "Unrecognized value of count attribute") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
retval = 1;
|
||||
done:
|
||||
if (reason)
|
||||
free(reason);
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! Retrieve collection configuration and device state information
|
||||
*
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] xe Request: <rpc><xn></rpc>
|
||||
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
|
||||
* @param[in] arg client-entry
|
||||
* @param[in] regarg User argument given at rpc_callback_register()
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*
|
||||
* @see from_client_get
|
||||
*/
|
||||
static int
|
||||
from_client_get_collection(clicon_handle h,
|
||||
cxobj *xe,
|
||||
cbuf *cbret,
|
||||
void *arg,
|
||||
void *regarg)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *x;
|
||||
char *xpath = NULL;
|
||||
cxobj *xret = NULL;
|
||||
cxobj **xvec = NULL;
|
||||
size_t xlen;
|
||||
cxobj *xnacm = NULL;
|
||||
char *username;
|
||||
cvec *nsc = NULL; /* Create a netconf namespace context from filter */
|
||||
char *attr;
|
||||
netconf_content content = CONTENT_ALL;
|
||||
int32_t depth = -1; /* Nr of levels to print, -1 is all, 0 is none */
|
||||
yang_stmt *yspec;
|
||||
int i;
|
||||
cxobj *xerr = NULL;
|
||||
int ret;
|
||||
uint32_t count = 0;
|
||||
uint32_t skip = 0;
|
||||
char *direction = NULL;
|
||||
char *sort = NULL;
|
||||
char *where = NULL;
|
||||
char *datastore = NULL;
|
||||
char *reason = NULL;
|
||||
cbuf *cb = NULL;
|
||||
cxobj *xtop = NULL;
|
||||
cxobj *xbot = NULL;
|
||||
yang_stmt *y;
|
||||
char *api_path = NULL;
|
||||
char *ns;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
username = clicon_username_get(h);
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "No yang spec9");
|
||||
goto done;
|
||||
}
|
||||
/* Clixon extensions: content */
|
||||
if ((attr = xml_find_value(xe, "content")) != NULL)
|
||||
content = netconf_content_str2int(attr);
|
||||
/* Clixon extensions: depth */
|
||||
if ((attr = xml_find_value(xe, "depth")) != NULL){
|
||||
if ((ret = parse_int32(attr, &depth, &reason)) < 0){
|
||||
clicon_err(OE_XML, errno, "parse_int32");
|
||||
goto done;
|
||||
}
|
||||
if (ret == 0){
|
||||
if (netconf_bad_attribute(cbret, "application",
|
||||
"depth", "Unrecognized value of depth attribute") < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
}
|
||||
/* count */
|
||||
if ((ret = element2value(h, xe, "count", cbret, &count)) < 0)
|
||||
goto done;
|
||||
/* skip */
|
||||
if (ret && (ret = element2value(h, xe, "skip", cbret, &skip)) < 0)
|
||||
goto done;
|
||||
/* direction */
|
||||
if (ret && (x = xml_find_type(xe, NULL, "direction", CX_ELMNT)) != NULL){
|
||||
direction = xml_body(x);
|
||||
if (strcmp(direction, "forward") != 0 && strcmp(direction, "reverse") != 0){
|
||||
if (netconf_bad_attribute(cbret, "application",
|
||||
"direction", "Unrecognized value of direction attribute") < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
}
|
||||
/* sort */
|
||||
if (ret && (x = xml_find_type(xe, NULL, "sort", CX_ELMNT)) != NULL)
|
||||
sort = xml_body(x);
|
||||
if (sort) ; /* XXX */
|
||||
/* where */
|
||||
if (ret && (x = xml_find_type(xe, NULL, "where", CX_ELMNT)) != NULL)
|
||||
where = xml_body(x);
|
||||
/* datastore */
|
||||
if (ret && (x = xml_find_type(xe, NULL, "datastore", CX_ELMNT)) != NULL)
|
||||
datastore = xml_body(x);
|
||||
if (ret == 0)
|
||||
goto ok;
|
||||
/* list-target, create (xml and) yang to check if is state (CF) or config (CT) */
|
||||
if (ret && (x = xml_find_type(xe, NULL, "list-target", CX_ELMNT)) != NULL)
|
||||
api_path = xml_body(x);
|
||||
if ((xtop = xml_new("top", NULL, CX_ELMNT)) == NULL)
|
||||
goto done;
|
||||
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 0, &xbot, &y, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
if (yang_keyword_get(y) != Y_LIST && yang_keyword_get(y) != Y_LEAF_LIST){
|
||||
if (netconf_bad_element(cbret, "application", "list-target", "path invalid") < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
/* Create xpath from api-path for the datastore API */
|
||||
if ((ret = api_path2xpath(api_path, yspec, &xpath, &nsc, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
/* Build a "predicate" cbuf */
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
cprintf(cb, "%s", xpath);
|
||||
if (where)
|
||||
cprintf(cb, "[%s]", where);
|
||||
if (skip){
|
||||
cprintf(cb, "[%u < position()", skip);
|
||||
if (count)
|
||||
cprintf(cb, " && position() < %u", count+skip);
|
||||
cprintf(cb, "]");
|
||||
}
|
||||
else if (count)
|
||||
cprintf(cb, "[position() < %u]", count);
|
||||
|
||||
/* Split into CT or CF */
|
||||
if (yang_config_ancestor(y) == 1){ /* CT */
|
||||
if (content == CONTENT_CONFIG || content == CONTENT_ALL){
|
||||
if (xmldb_get0(h, datastore, YB_MODULE, nsc, cbuf_get(cb), 1, &xret, NULL) < 0) {
|
||||
if (netconf_operation_failed(cbret, "application", "read registry")< 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
}
|
||||
/* There may be CF data in a CT collection */
|
||||
if (content == CONTENT_ALL){
|
||||
if ((ret = client_statedata(h, cbuf_get(cb), nsc, content, &xret)) < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
else { /* CF */
|
||||
/* There can be no CT data in a CF collection */
|
||||
if (content == CONTENT_NONCONFIG || content == CONTENT_ALL){
|
||||
if ((ret = client_statedata(h, cbuf_get(cb), nsc, content, &xret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){ /* Error from callback (error in xret) */
|
||||
if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){
|
||||
/* Check XML by validating it. return internal error with error cause
|
||||
* Primarily intended for user-supplied state-data.
|
||||
* The whole config tree must be present in case the state data references config data
|
||||
*/
|
||||
if ((ret = xml_yang_validate_all_top(h, xret, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret > 0 &&
|
||||
(ret = xml_yang_validate_add(h, xret, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
if (clicon_debug_get())
|
||||
clicon_log_xml(LOG_DEBUG, xret, "VALIDATE_STATE");
|
||||
if (clixon_netconf_internal_error(xerr,
|
||||
". Internal error, state callback returned invalid XML",
|
||||
NULL) < 0)
|
||||
goto done;
|
||||
if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
} /* CLICON_VALIDATE_STATE_XML */
|
||||
|
||||
if (content == CONTENT_NONCONFIG){ /* state only, all config should be removed now */
|
||||
/* Keep state data only, remove everything that is not config. Note that state data
|
||||
* may be a sub-part in a config tree, we need to traverse to find all
|
||||
*/
|
||||
if (xml_non_config_data(xret, NULL) < 0)
|
||||
goto done;
|
||||
if (xml_tree_prune_flagged_sub(xret, XML_FLAG_MARK, 1, NULL) < 0)
|
||||
goto done;
|
||||
if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* Code complex to filter out anything that is outside of xpath
|
||||
* Actually this is a safety catch, should really be done in plugins
|
||||
* and modules_state functions.
|
||||
*/
|
||||
if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
|
||||
goto done;
|
||||
|
||||
/* Pre-NACM access step */
|
||||
xnacm = clicon_nacm_cache(h);
|
||||
if (xnacm != NULL){ /* Do NACM validation */
|
||||
/* NACM datanode/module read validation */
|
||||
if (nacm_datanode_read(h, xret, xvec, xlen, username, xnacm) < 0)
|
||||
goto done;
|
||||
}
|
||||
cprintf(cbret, "<rpc-reply xmlns=\"%s\"><collection xmlns=\"%s\">",
|
||||
NETCONF_BASE_NAMESPACE, NETCONF_COLLECTION_NAMESPACE); /* OK */
|
||||
if ((ns = yang_find_mynamespace(y)) != NULL)
|
||||
for (i=0; i<xlen; i++){
|
||||
x = xvec[i];
|
||||
/* Add namespace */
|
||||
if (xmlns_set(x, NULL, ns) < 0)
|
||||
goto done;
|
||||
/* Top level is data, so add 1 to depth if significant */
|
||||
if (clicon_xml2cbuf(cbret, x, 0, 0, depth>0?depth+1:depth) < 0)
|
||||
goto done;
|
||||
}
|
||||
cprintf(cbret, "</collection></rpc-reply>");
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
||||
if (xtop)
|
||||
xml_free(xtop);
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
if (reason)
|
||||
free(reason);
|
||||
if (xerr)
|
||||
xml_free(xerr);
|
||||
if (xvec)
|
||||
free(xvec);
|
||||
if (nsc)
|
||||
xml_nsctx_free(nsc);
|
||||
if (xret)
|
||||
xml_free(xret);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Create a notification subscription
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] xe Request: <rpc><xn></rpc>
|
||||
|
|
@ -1960,7 +2259,10 @@ backend_rpc_init(clicon_handle h)
|
|||
if (rpc_callback_register(h, from_client_validate, NULL,
|
||||
NETCONF_BASE_NAMESPACE, "validate") < 0)
|
||||
goto done;
|
||||
|
||||
/* draft-ietf-netconf-restconf-collection-00 */
|
||||
if (rpc_callback_register(h, from_client_get_collection, NULL,
|
||||
NETCONF_COLLECTION_NAMESPACE, "get-collection") < 0)
|
||||
goto done;
|
||||
/* In backend_client.? RPC from RFC 5277 */
|
||||
if (rpc_callback_register(h, from_client_create_subscription, NULL,
|
||||
EVENT_RFC5277_NAMESPACE, "create-subscription") < 0)
|
||||
|
|
|
|||
|
|
@ -42,13 +42,19 @@ extern "C" {
|
|||
#define _CLIXON_RESTCONF_H_
|
||||
|
||||
/*
|
||||
* Types (also in restconf_lib.h)
|
||||
* Types
|
||||
*/
|
||||
/*! RESTCONF media types
|
||||
* @see http_media_map
|
||||
* @note DUPLICATED in clixon_lib.h
|
||||
*/
|
||||
enum restconf_media{
|
||||
YANG_DATA_JSON, /* "application/yang-data+json" */
|
||||
YANG_DATA_XML, /* "application/yang-data+xml" */
|
||||
YANG_PATCH_JSON, /* "application/yang-patch+json" */
|
||||
YANG_PATCH_XML /* "application/yang-patch+xml" */
|
||||
YANG_DATA_JSON, /* "application/yang-data+json" */
|
||||
YANG_DATA_XML, /* "application/yang-data+xml" */
|
||||
YANG_PATCH_JSON, /* "application/yang-patch+json" */
|
||||
YANG_PATCH_XML, /* "application/yang-patch+xml" */
|
||||
YANG_COLLECTION_XML, /* draft-ietf-netconf-restconf-collection-00.txt */
|
||||
YANG_COLLECTION_JSON /* draft-ietf-netconf-restconf-collection-00.txt */
|
||||
};
|
||||
typedef enum restconf_media restconf_media;
|
||||
|
||||
|
|
|
|||
|
|
@ -275,6 +275,7 @@ api_return_err(clicon_handle h,
|
|||
switch (media){
|
||||
case YANG_DATA_XML:
|
||||
case YANG_PATCH_XML:
|
||||
case YANG_COLLECTION_XML:
|
||||
clicon_debug(1, "%s code:%d", __FUNCTION__, code);
|
||||
if (pretty){
|
||||
cprintf(cb, " <errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">\n");
|
||||
|
|
@ -291,6 +292,7 @@ api_return_err(clicon_handle h,
|
|||
break;
|
||||
case YANG_DATA_JSON:
|
||||
case YANG_PATCH_JSON:
|
||||
case YANG_COLLECTION_JSON:
|
||||
clicon_debug(1, "%s code:%d", __FUNCTION__, code);
|
||||
if (pretty){
|
||||
cprintf(cb, "{\n\"ietf-restconf:errors\" : ");
|
||||
|
|
@ -306,9 +308,7 @@ api_return_err(clicon_handle h,
|
|||
cprintf(cb, "}\r\n");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
clicon_err(OE_YANG, EINVAL, "Invalid media type %d", media);
|
||||
goto done;
|
||||
default: /* Just ignore the body so that there is a reply */
|
||||
break;
|
||||
} /* switch media */
|
||||
assert(cbuf_len(cb));
|
||||
|
|
|
|||
|
|
@ -203,10 +203,12 @@ static const map_str2int http_reason_phrase_map[] = {
|
|||
* @see restconf_media_str2int
|
||||
*/
|
||||
static const map_str2int http_media_map[] = {
|
||||
{"application/yang-data+xml", YANG_DATA_XML},
|
||||
{"application/yang-data+json", YANG_DATA_JSON},
|
||||
{"application/yang-patch+xml", YANG_PATCH_XML},
|
||||
{"application/yang-patch+json", YANG_PATCH_JSON},
|
||||
{"application/yang-data+xml", YANG_DATA_XML},
|
||||
{"application/yang-data+json", YANG_DATA_JSON},
|
||||
{"application/yang-patch+xml", YANG_PATCH_XML},
|
||||
{"application/yang-patch+json", YANG_PATCH_JSON},
|
||||
{"application/yang.collection+xml", YANG_COLLECTION_XML},
|
||||
{"application/yang.collection+json", YANG_COLLECTION_JSON},
|
||||
{NULL, -1}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -46,13 +46,15 @@ extern "C" {
|
|||
*/
|
||||
/*! RESTCONF media types
|
||||
* @see http_media_map
|
||||
* (also in clixon_restconf.h)
|
||||
* @note DUPLICATED in clixon_restconf.h
|
||||
*/
|
||||
enum restconf_media{
|
||||
YANG_DATA_JSON, /* "application/yang-data+json" */
|
||||
YANG_DATA_XML, /* "application/yang-data+xml" */
|
||||
YANG_PATCH_JSON, /* "application/yang-patch+json" */
|
||||
YANG_PATCH_XML /* "application/yang-patch+xml" */
|
||||
YANG_PATCH_XML, /* "application/yang-patch+xml" */
|
||||
YANG_COLLECTION_XML, /* draft-ietf-netconf-restconf-collection-00.txt */
|
||||
YANG_COLLECTION_JSON /* draft-ietf-netconf-restconf-collection-00.txt */
|
||||
};
|
||||
typedef enum restconf_media restconf_media;
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@
|
|||
* @param[in] media_out Output media
|
||||
* @param[in] head If 1 is HEAD, otherwise GET
|
||||
* @code
|
||||
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
|
||||
* curl -X GET http://localhost/restconf/data/interfaces/interface=eth0
|
||||
* @endcode
|
||||
* See RFC8040 Sec 4.2 and 4.3
|
||||
* XXX: cant find a way to use Accept request field to choose Content-Type
|
||||
|
|
@ -215,7 +215,8 @@ api_data_get2(clicon_handle h,
|
|||
goto ok;
|
||||
}
|
||||
/* Normal return, no error */
|
||||
if ((cbx = cbuf_new()) == NULL)
|
||||
if ((cbx = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
if (xpath==NULL || strcmp(xpath,"/")==0){ /* Special case: data root */
|
||||
switch (media_out){
|
||||
|
|
@ -228,6 +229,9 @@ api_data_get2(clicon_handle h,
|
|||
goto done;
|
||||
break;
|
||||
default:
|
||||
if (restconf_unsupported_media(req) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -309,6 +313,227 @@ api_data_get2(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! GET Collection
|
||||
* According to restconf collection draft. Lists, work in progress
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] req Generic Www handle
|
||||
* @param[in] api_path 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 path starts
|
||||
* @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] head If 1 is HEAD, otherwise GET
|
||||
* @code
|
||||
* curl -X GET http://localhost/restconf/data/interfaces
|
||||
* @endcode
|
||||
* A collection resource contains a set of data resources. It is used
|
||||
* to represent a all instances or a subset of all instances in a YANG
|
||||
* list or leaf-list.
|
||||
* @see draft-ietf-netconf-restconf-collection-00.txt
|
||||
*/
|
||||
static int
|
||||
api_data_collection(clicon_handle h,
|
||||
void *req,
|
||||
char *api_path,
|
||||
cvec *pcvec, /* XXX remove? */
|
||||
int pi,
|
||||
cvec *qvec,
|
||||
int pretty,
|
||||
restconf_media media_out)
|
||||
{
|
||||
int retval = -1;
|
||||
char *xpath = NULL;
|
||||
cbuf *cbx = NULL;
|
||||
yang_stmt *yspec;
|
||||
cxobj *xret = NULL;
|
||||
cxobj *xerr = NULL; /* malloced */
|
||||
cxobj *xe = NULL; /* not malloced */
|
||||
cxobj **xvec = NULL;
|
||||
int i;
|
||||
int ret;
|
||||
cvec *nsc = NULL;
|
||||
char *attr; /* attribute value string */
|
||||
netconf_content content = CONTENT_ALL;
|
||||
cxobj *xtop = NULL;
|
||||
cxobj *xbot = NULL;
|
||||
yang_stmt *y = NULL;
|
||||
cbuf *cbrpc = NULL;
|
||||
char *depth;
|
||||
char *count;
|
||||
char *skip;
|
||||
char *direction;
|
||||
char *sort;
|
||||
char *where;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "No DB_SPEC");
|
||||
goto done;
|
||||
}
|
||||
/* strip /... from start */
|
||||
for (i=0; i<pi; i++)
|
||||
api_path = index(api_path+1, '/');
|
||||
if (api_path){
|
||||
if ((xtop = xml_new("top", NULL, CX_ELMNT)) == NULL)
|
||||
goto done;
|
||||
/* Translate api-path to xml, but to validate the api-path, note: strict=1
|
||||
* xtop and xbot unnecessary for this function but needed by function
|
||||
* Set strict=0 to accept list uri:s with =keys syntax
|
||||
*/
|
||||
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 0, &xbot, &y, &xerr)) < 0)
|
||||
goto done;
|
||||
/* Translate api-path to xpath: xpath (cbpath) and namespace context (nsc)
|
||||
* XXX: xpath not used in collection?
|
||||
*/
|
||||
if (ret != 0 &&
|
||||
(ret = api_path2xpath(api_path, yspec, &xpath, &nsc, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){ /* validation failed */
|
||||
if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){
|
||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||
goto done;
|
||||
}
|
||||
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
if (yang_keyword_get(y) != Y_LIST && yang_keyword_get(y) != Y_LEAF_LIST){
|
||||
if (netconf_bad_element_xml(&xerr, "application",
|
||||
yang_argument_get(y),
|
||||
"Element is not list or leaf-list which is required for GET collection") < 0)
|
||||
goto done;
|
||||
if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){
|
||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||
goto done;
|
||||
}
|
||||
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for content attribute */
|
||||
if ((attr = cvec_find_str(qvec, "content")) != NULL){
|
||||
clicon_debug(1, "%s content=%s", __FUNCTION__, attr);
|
||||
if ((int)(content = netconf_content_str2int(attr)) == -1){
|
||||
if (netconf_bad_attribute_xml(&xerr, "application",
|
||||
"content", "Unrecognized value of content attribute") < 0)
|
||||
goto done;
|
||||
if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){
|
||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||
goto done;
|
||||
}
|
||||
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
}
|
||||
clicon_debug(1, "%s path:%s", __FUNCTION__, xpath);
|
||||
if (content != CONTENT_CONFIG && content != CONTENT_NONCONFIG && content != CONTENT_ALL){
|
||||
clicon_err(OE_XML, EINVAL, "Invalid content attribute %d", content);
|
||||
goto done;
|
||||
}
|
||||
/* Clixon extensions and collection attributes */
|
||||
depth = cvec_find_str(qvec, "depth");
|
||||
count = cvec_find_str(qvec, "count");
|
||||
skip = cvec_find_str(qvec, "skip");
|
||||
direction = cvec_find_str(qvec, "direction");
|
||||
sort = cvec_find_str(qvec, "sort");
|
||||
where = cvec_find_str(qvec, "where");
|
||||
|
||||
if (clicon_rpc_get_collection(h, api_path, y, nsc, content,
|
||||
depth, count, skip, direction, sort, where,
|
||||
&xret) < 0){
|
||||
if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0)
|
||||
goto done;
|
||||
if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){
|
||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||
goto done;
|
||||
}
|
||||
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
|
||||
/* We get return via netconf which is complete tree from root
|
||||
* We need to cut that tree to only the object.
|
||||
*/
|
||||
#if 0 /* DEBUG */
|
||||
if (clicon_debug_get())
|
||||
clicon_log_xml(LOG_DEBUG, xret, "%s xret:", __FUNCTION__);
|
||||
#endif
|
||||
/* Check if error return */
|
||||
if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){
|
||||
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
/* Normal return, no error */
|
||||
if ((cbx = cbuf_new()) == NULL)
|
||||
goto done;
|
||||
switch (media_out){
|
||||
case YANG_COLLECTION_XML:
|
||||
if (clicon_xml2cbuf(cbx, xret, 0, pretty, -1) < 0) /* Dont print top object? */
|
||||
goto done;
|
||||
break;
|
||||
case YANG_COLLECTION_JSON:
|
||||
if (xml2json_cbuf(cbx, xret, pretty) < 0)
|
||||
goto done;
|
||||
break;
|
||||
default:
|
||||
if (restconf_unsupported_media(req) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
break;
|
||||
}
|
||||
#if 0
|
||||
/* Check if not exists */
|
||||
if (xlen == 0){
|
||||
/* 4.3: If a retrieval request for a data resource represents an
|
||||
instance that does not exist, then an error response containing
|
||||
a "404 Not Found" status-line MUST be returned by the server.
|
||||
The error-tag value "invalid-value" is used in this case. */
|
||||
if (netconf_invalid_value_xml(&xerr, "application", "Instance does not exist") < 0)
|
||||
goto done;
|
||||
/* override invalid-value default 400 with 404 */
|
||||
if ((xe = xpath_first(xerr, NULL, "rpc-error")) != NULL){
|
||||
if (api_return_err(h, req, xe, pretty, media_out, 404) < 0)
|
||||
goto done;
|
||||
}
|
||||
goto ok;
|
||||
}
|
||||
#endif
|
||||
clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx));
|
||||
if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0)
|
||||
goto done;
|
||||
if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0)
|
||||
goto done;
|
||||
if (restconf_reply_send(req, 200, cbx) < 0)
|
||||
goto done;
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
||||
if (cbrpc)
|
||||
cbuf_free(cbrpc);
|
||||
if (xpath)
|
||||
free(xpath);
|
||||
if (nsc)
|
||||
xml_nsctx_free(nsc);
|
||||
if (xtop)
|
||||
xml_free(xtop);
|
||||
if (cbx)
|
||||
cbuf_free(cbx);
|
||||
if (xret)
|
||||
xml_free(xret);
|
||||
if (xerr)
|
||||
xml_free(xerr);
|
||||
if (xvec)
|
||||
free(xvec);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! REST HEAD method
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] req Generic Www handle
|
||||
|
|
@ -375,7 +600,27 @@ api_data_get(clicon_handle h,
|
|||
restconf_media media_out,
|
||||
ietf_ds_t ds)
|
||||
{
|
||||
return api_data_get2(h, req, api_path, pcvec, pi, qvec, pretty, media_out, 0);
|
||||
int retval = -1;
|
||||
|
||||
switch (media_out){
|
||||
case YANG_DATA_XML:
|
||||
case YANG_DATA_JSON:
|
||||
if (api_data_get2(h, req, api_path, pcvec, pi, qvec, pretty, media_out, 0) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case YANG_COLLECTION_XML:
|
||||
case YANG_COLLECTION_JSON:
|
||||
if (api_data_collection(h, req, api_path, pcvec, pi, qvec, pretty, media_out) < 0)
|
||||
goto done;
|
||||
break;
|
||||
default:
|
||||
if (restconf_unsupported_media(req) < 0)
|
||||
goto done;
|
||||
break;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! GET restconf/operations resource
|
||||
|
|
|
|||
|
|
@ -432,6 +432,7 @@ api_root_restconf(clicon_handle h,
|
|||
cbuf *cb = NULL;
|
||||
char *media_str = NULL;
|
||||
restconf_media media_out = YANG_DATA_JSON;
|
||||
restconf_media media_in;
|
||||
char *indata = NULL;
|
||||
char *username = NULL;
|
||||
int ret;
|
||||
|
|
@ -468,7 +469,6 @@ api_root_restconf(clicon_handle h,
|
|||
goto ok;
|
||||
}
|
||||
}
|
||||
|
||||
clicon_debug(1, "%s ACCEPT: %s %s", __FUNCTION__, media_str, restconf_media_int2str(media_out));
|
||||
|
||||
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
|
||||
|
|
|
|||
|
|
@ -55,6 +55,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, cvec *nsc, netconf_content content, int32_t depth, cxobj **xret);
|
||||
int clicon_rpc_get_collection(clicon_handle h, char *apipath, yang_stmt *yco, cvec *nsc, netconf_content content, char *depth, char *count, char *skip, char *direction, char *sort, char *where, cxobj **xt);
|
||||
int clicon_rpc_close_session(clicon_handle h);
|
||||
int clicon_rpc_kill_session(clicon_handle h, uint32_t session_id);
|
||||
int clicon_rpc_validate(clicon_handle h, char *db);
|
||||
|
|
|
|||
|
|
@ -54,6 +54,10 @@
|
|||
*/
|
||||
#define NETCONF_INPUT_CONFIG "config"
|
||||
|
||||
/* Collections namespace from draft-ietf-netconf-restconf-collection-00.txt
|
||||
*/
|
||||
#define NETCONF_COLLECTION_NAMESPACE "urn:ietf:params:xml:ns:yang:ietf-netconf-collection"
|
||||
|
||||
/* Output symbol for netconf get/get-config
|
||||
* ietf-netconf.yang defines it as output:
|
||||
* output { anyxml data;
|
||||
|
|
|
|||
|
|
@ -1526,6 +1526,10 @@ netconf_module_load(clicon_handle h)
|
|||
if (clicon_option_bool(h, "CLICON_NETCONF_MESSAGE_ID_OPTIONAL") == 1)
|
||||
xml_bind_netconf_message_id_optional(1);
|
||||
#endif
|
||||
/* Load restconf collection */
|
||||
if (yang_spec_parse_module(h, "ietf-netconf-collection", NULL, yspec)< 0)
|
||||
goto done;
|
||||
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
|
|
|
|||
|
|
@ -827,9 +827,10 @@ api_path2xpath_cvv(cvec *api_path,
|
|||
* @retval 0 Invalid api_path or associated XML, netconf called
|
||||
* @retval -1 Fatal error, clicon_err called
|
||||
* @code
|
||||
* char *xpath = NULL;
|
||||
* cvec *nsc = NULL;
|
||||
* if ((ret = api_path2xpath("/module:a/b", yspec, &xpath, &nsc)) < 0)
|
||||
* char *xpath = NULL;
|
||||
* cvec *nsc = NULL;
|
||||
* cxobj *xerr = NULL;
|
||||
* if ((ret = api_path2xpath("/module:a/b", yspec, &xpath, &nsc, &xerr)) < 0)
|
||||
* err;
|
||||
* if (ret == 1)
|
||||
* ... access xpath as cbuf_get(xpath)
|
||||
|
|
@ -896,6 +897,7 @@ api_path2xpath(char *api_path,
|
|||
* @param[in] x0 Xpath tree so far
|
||||
* @param[in] y0 Yang spec for x0
|
||||
* @param[in] nodeclass Set to schema nodes, data nodes, etc
|
||||
* @param[in] strict Break if api-path is not "complete" otherwise ignore and continue
|
||||
* @param[out] xbotp Resulting xml tree
|
||||
* @param[out] ybotp Yang spec matching xpathp
|
||||
* @param[out] xerr Netconf error message (if retval=0)
|
||||
|
|
@ -1019,10 +1021,6 @@ api_path2xml_vec(char **vec,
|
|||
break;
|
||||
case Y_LIST:
|
||||
cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */
|
||||
if (valvec){ /* loop, valvec may have been used before */
|
||||
free(valvec);
|
||||
valvec = NULL;
|
||||
}
|
||||
if (restval==NULL){
|
||||
if (strict){
|
||||
cprintf(cberr, "malformed key =%s, expected '=restval'", nodeid);
|
||||
|
|
@ -1111,7 +1109,7 @@ api_path2xml_vec(char **vec,
|
|||
}
|
||||
if ((retval = api_path2xml_vec(vec+1, nvec-1,
|
||||
x, y,
|
||||
nodeclass, strict,
|
||||
nodeclass, strict,
|
||||
xbotp, ybotp, xerr)) < 1)
|
||||
goto done;
|
||||
ok:
|
||||
|
|
@ -1142,6 +1140,7 @@ api_path2xml_vec(char **vec,
|
|||
* @param[in] yspec Yang spec
|
||||
* @param[in,out] xtop Incoming XML tree
|
||||
* @param[in] nodeclass Set to schema nodes, data nodes, etc
|
||||
* @param[in] strict Break if api-path is not "complete" otherwise ignore and continue
|
||||
* @param[out] xbotp Resulting xml tree (end of xpath)
|
||||
* @param[out] ybotp Yang spec matching xbotp
|
||||
* @param[out] xerr Netconf error message (if retval=0)
|
||||
|
|
@ -1163,6 +1162,7 @@ api_path2xml_vec(char **vec,
|
|||
* @endcode
|
||||
* @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
|
||||
* @see api_path2xpath For api-path to xpath translation (maybe could be combined?)
|
||||
* @note "Collections" should use strict = 0
|
||||
*/
|
||||
int
|
||||
api_path2xml(char *api_path,
|
||||
|
|
|
|||
|
|
@ -823,6 +823,7 @@ clicon_rpc_get(clicon_handle h,
|
|||
if (depth != -1)
|
||||
cprintf(cb, " depth=\"%d\"", depth);
|
||||
cprintf(cb, ">");
|
||||
/* If xpath, add a filter */
|
||||
if (xpath && strlen(xpath)) {
|
||||
cprintf(cb, "<%s:filter %s:type=\"xpath\" %s:select=\"%s\"",
|
||||
NETCONF_BASE_PREFIX, NETCONF_BASE_PREFIX, NETCONF_BASE_PREFIX,
|
||||
|
|
@ -877,7 +878,134 @@ clicon_rpc_get(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Get database configuration and state data collection
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] apipath To identify a list/leaf-list
|
||||
* @param[in] yli Yang-stmt of list/leaf-list of collection
|
||||
* @param[in] namespace Namespace associated w xpath
|
||||
* @param[in] nsc Namespace context for filter
|
||||
* @param[in] content Clixon extension: all, config, noconfig. -1 means all
|
||||
* @param[in] depth Nr of XML levels to get, -1 is all, 0 is none
|
||||
* @param[in] count Collection/clixon extension
|
||||
* @param[in] skip Collection/clixon extension
|
||||
* @param[in] direction Collection/clixon extension
|
||||
* @param[in] sort Collection/clixon extension
|
||||
* @param[in] where Collection/clixon extension
|
||||
* @param[out] xt XML tree. Free with xml_free.
|
||||
* Either <config> or <rpc-error>.
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error, fatal or xml
|
||||
|
||||
* @see clicon_rpc_get
|
||||
* @see draft-ietf-netconf-restconf-collection-00
|
||||
* @note the netconf return message is yang populated, as well as the return data
|
||||
*/
|
||||
int
|
||||
clicon_rpc_get_collection(clicon_handle h,
|
||||
char *apipath,
|
||||
yang_stmt *yli,
|
||||
cvec *nsc, /* namespace context for filter */
|
||||
netconf_content content,
|
||||
char *depth,
|
||||
char *count,
|
||||
char *skip,
|
||||
char *direction,
|
||||
char *sort,
|
||||
char *where,
|
||||
cxobj **xt)
|
||||
{
|
||||
int retval = -1;
|
||||
struct clicon_msg *msg = NULL;
|
||||
cbuf *cb = NULL;
|
||||
cxobj *xret = NULL;
|
||||
cxobj *xerr = NULL;
|
||||
cxobj *xr;
|
||||
char *username;
|
||||
uint32_t session_id;
|
||||
int ret;
|
||||
yang_stmt *yspec;
|
||||
cxobj *x;
|
||||
|
||||
if (session_id_check(h, &session_id) < 0)
|
||||
goto done;
|
||||
if ((cb = cbuf_new()) == NULL)
|
||||
goto done;
|
||||
cprintf(cb, "<rpc xmlns=\"%s\" ", NETCONF_BASE_NAMESPACE);
|
||||
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-collection xmlns=\"%s\"", NETCONF_COLLECTION_NAMESPACE);
|
||||
/* Clixon extension, content=all,config, or nonconfig */
|
||||
if ((int)content != -1)
|
||||
cprintf(cb, " content=\"%s\"", netconf_content_int2str(content));
|
||||
if (depth)
|
||||
cprintf(cb, " depth=\"%s\"", depth);
|
||||
cprintf(cb, ">");
|
||||
if (count)
|
||||
cprintf(cb, "<list-target>%s</list-target>", apipath);
|
||||
if (count)
|
||||
cprintf(cb, "<count>%s</count>", count);
|
||||
if (skip)
|
||||
cprintf(cb, "<skip>%s</skip>", skip);
|
||||
if (direction)
|
||||
cprintf(cb, "<direction>%s</direction>", direction);
|
||||
if (sort)
|
||||
cprintf(cb, "<sort>%s</sort>", sort);
|
||||
if (where)
|
||||
cprintf(cb, "<where>%s</where>", where);
|
||||
cprintf(cb, "</get-collection></rpc>");
|
||||
if ((msg = clicon_msg_encode(session_id, "%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 ((xr = xpath_first(xret, NULL, "/rpc-reply/rpc-error")) != NULL)
|
||||
xr = xml_parent(xr); /* point to rpc-reply */
|
||||
else if ((xr = xpath_first(xret, NULL, "/rpc-reply/collection")) == NULL){
|
||||
if ((xr = xml_new("collection", NULL, CX_ELMNT)) == NULL)
|
||||
goto done;
|
||||
}
|
||||
else{
|
||||
yspec = clicon_dbspec_yang(h);
|
||||
/* Populate all children with yco */
|
||||
x = NULL;
|
||||
while ((x = xml_child_each(xr, x, CX_ELMNT)) != NULL){
|
||||
xml_spec_set(x, yli);
|
||||
if ((ret = xml_bind_yang(x, YB_PARENT, yspec, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
if (clixon_netconf_internal_error(xerr,
|
||||
". Internal error, backend returned invalid XML.",
|
||||
NULL) < 0)
|
||||
goto done;
|
||||
if ((xr = xpath_first(xerr, NULL, "rpc-error")) == NULL){
|
||||
clicon_err(OE_XML, ENOENT, "Expected rpc-error tag but none found(internal)");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (xr){
|
||||
if (xml_rm(xr) < 0)
|
||||
goto done;
|
||||
*xt = xr;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
if (xerr)
|
||||
xml_free(xerr);
|
||||
if (xret)
|
||||
xml_free(xret);
|
||||
if (msg)
|
||||
free(msg);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Send a close a netconf user session. Socket is also closed if still open
|
||||
*
|
||||
* @param[in] h CLICON handle
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error and logged to syslog
|
||||
|
|
|
|||
|
|
@ -743,4 +743,3 @@ xml_bind_yang_rpc_reply(cxobj *xrpc,
|
|||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
|
|
|
|||
114
test/test_collection.sh
Executable file
114
test/test_collection.sh
Executable file
|
|
@ -0,0 +1,114 @@
|
|||
#!/usr/bin/env bash
|
||||
# Restconf RFC8040 Appendix A and B "jukebox" example
|
||||
# For collection / scaling activity
|
||||
# Magic line must be first in script (see README.md)
|
||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
||||
APPNAME=example
|
||||
|
||||
cfg=$dir/conf.xml
|
||||
fjukebox=$dir/example-jukebox.yang
|
||||
|
||||
cat <<EOF > $cfg
|
||||
<clixon-config xmlns="http://clicon.org/config">
|
||||
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
|
||||
<CLICON_FEATURE>ietf-netconf:startup</CLICON_FEATURE>
|
||||
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_MAIN_DIR>$dir</CLICON_YANG_MAIN_DIR>
|
||||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
|
||||
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
|
||||
<CLICON_BACKEND_PIDFILE>$dir/restconf.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
|
||||
<CLICON_STREAM_DISCOVERY_RFC8040>true</CLICON_STREAM_DISCOVERY_RFC8040>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
cat <<EOF > $dir/startup_db
|
||||
<config>
|
||||
<jukebox xmlns="http://example.com/ns/example-jukebox">
|
||||
<library>
|
||||
<artist>
|
||||
<name>Foo Fighters</name>
|
||||
<album xmlns="http://example.com/ns/example-jukebox">
|
||||
<name>Crime and Punishment</name>
|
||||
<year>1995</year>
|
||||
</album>
|
||||
<album xmlns="http://example.com/ns/example-jukebox">
|
||||
<name>One by One</name>
|
||||
<year>2002</year>
|
||||
</album>
|
||||
<album xmlns="http://example.com/ns/example-jukebox">
|
||||
<name>The Color and the Shape</name>
|
||||
<year>1997</year>
|
||||
</album>
|
||||
<album xmlns="http://example.com/ns/example-jukebox">
|
||||
<name>There is Nothing Left to Loose</name>
|
||||
<year>1999</year>
|
||||
</album>
|
||||
<album xmlns="http://example.com/ns/example-jukebox">
|
||||
<name>White and Black</name>
|
||||
<year>1998</year>
|
||||
</album>
|
||||
</artist>
|
||||
</library>
|
||||
</jukebox>
|
||||
</config>
|
||||
EOF
|
||||
|
||||
# Common Jukebox spec (fjukebox must be set)
|
||||
. ./jukebox.sh
|
||||
|
||||
new "test params: -f $cfg -- -s" # XXX: -sS state file
|
||||
|
||||
if [ $BE -ne 0 ]; then
|
||||
new "kill old backend"
|
||||
sudo clixon_backend -zf $cfg
|
||||
if [ $? -ne 0 ]; then
|
||||
err
|
||||
fi
|
||||
sudo pkill -f clixon_backend # to be sure
|
||||
new "start backend -s startup -f $cfg"
|
||||
start_backend -s startup -f "$cfg"
|
||||
fi
|
||||
|
||||
new "waiting"
|
||||
wait_backend
|
||||
|
||||
if [ $RC -ne 0 ]; then
|
||||
new "kill old restconf daemon"
|
||||
stop_restconf_pre
|
||||
|
||||
new "start restconf daemon"
|
||||
start_restconf -f $cfg
|
||||
|
||||
new "waiting"
|
||||
wait_restconf
|
||||
fi
|
||||
|
||||
new "C.1. 'count' Parameter RESTCONF"
|
||||
expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang.collection+xml" $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album/?count=2)" 0 "HTTP/1.1 200 OK" "application/yang.collection+xml" '<collection xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-collection"><album xmlns="http://example.com/ns/example-jukebox"><name>Crime and Punishment</name><year>1995</year></album><album xmlns="http://example.com/ns/example-jukebox"><name>One by One</name><year>2002</year></album></collection>'
|
||||
|
||||
new "C.1. 'count' Parameter NETCONF"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc netconf:message-id=\"101\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><get-collection xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-collection\"><datastore>running</datastore><module-name>example-jukebox</module-name><list-target>/example-jukebox:jukebox/library/artist=Foo Fighters/album</list-target><count>2</count></get-collection></rpc>]]>]]>" '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" netconf:message-id="101"><collection xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-collection"><album xmlns="http://example.com/ns/example-jukebox"><name>Crime and Punishment</name><year>1995</year></album><album xmlns="http://example.com/ns/example-jukebox"><name>One by One</name><year>2002</year></album></collection></rpc-reply>]]>]]>$'
|
||||
|
||||
if [ $RC -ne 0 ]; then
|
||||
new "Kill restconf daemon"
|
||||
stop_restconf
|
||||
fi
|
||||
|
||||
if [ $BE -eq 0 ]; then
|
||||
exit # BE
|
||||
fi
|
||||
|
||||
new "Kill backend"
|
||||
# Check if premature kill
|
||||
pid=$(pgrep -u root -f clixon_backend)
|
||||
if [ -z "$pid" ]; then
|
||||
err "backend already dead"
|
||||
fi
|
||||
# kill backend
|
||||
stop_backend -f $cfg
|
||||
|
||||
rm -rf $dir
|
||||
125
yang/clixon/ietf-netconf-collection@2020-10-22.yang
Normal file
125
yang/clixon/ietf-netconf-collection@2020-10-22.yang
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
module ietf-netconf-collection {
|
||||
namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-collection";
|
||||
prefix "rcoll";
|
||||
|
||||
organization
|
||||
"IETF NETCONF (Network Configuration) Working Group";
|
||||
|
||||
contact
|
||||
"WG Web: <http://tools.ietf.org/wg/netconf/>
|
||||
WG List: <mailto:netconf@ietf.org>
|
||||
|
||||
WG Chair: Mehmet Ersue
|
||||
<mailto:mehmet.ersue@nsn.com>
|
||||
|
||||
WG Chair: Mahesh Jethanandani
|
||||
<mailto:mjethanandani@gmail.com>
|
||||
|
||||
Editor: Andy Bierman
|
||||
<mailto:andy@yumaworks.com>
|
||||
|
||||
Editor: Martin Bjorklund
|
||||
<mailto:mbj@tail-f.com>
|
||||
|
||||
Editor: Kent Watsen
|
||||
<mailto:kwatsen@juniper.net>";
|
||||
|
||||
description
|
||||
"This module contains conceptual YANG specifications
|
||||
for the RESTCONF Collection resource type.
|
||||
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) 2015 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 XXXX; see
|
||||
the RFC itself for full legal notices.";
|
||||
/*
|
||||
module: ietf-netconf-collection
|
||||
rpcs:
|
||||
|
||||
+---x get-collection
|
||||
+--w input
|
||||
+--w module-name string
|
||||
+--w datastore string
|
||||
+--w list-target string
|
||||
+--w count uint32
|
||||
+--w skip uint32
|
||||
+--w direction enum
|
||||
+--w sort string
|
||||
+--w where string
|
||||
+--ro output
|
||||
+-ro collection anydata
|
||||
*/
|
||||
|
||||
|
||||
revision 2020-10-22 {
|
||||
description
|
||||
"Draft by Olof Hagsand / Clixon.";
|
||||
}
|
||||
rpc get-collection {
|
||||
input {
|
||||
leaf module-name {
|
||||
/* Not needed with proper list-target */
|
||||
type string;
|
||||
}
|
||||
leaf datastore {
|
||||
type string;
|
||||
default "running";
|
||||
}
|
||||
leaf list-target {
|
||||
description "api-path";
|
||||
mandatory true;
|
||||
type string;
|
||||
}
|
||||
leaf count {
|
||||
type union {
|
||||
type uint32;
|
||||
type string {
|
||||
pattern 'unbounded';
|
||||
}
|
||||
}
|
||||
}
|
||||
leaf skip {
|
||||
type union {
|
||||
type uint32;
|
||||
type string {
|
||||
pattern 'unbounded';
|
||||
}
|
||||
}
|
||||
}
|
||||
leaf direction {
|
||||
type enumeration {
|
||||
enum forward;
|
||||
enum reverse;
|
||||
}
|
||||
}
|
||||
leaf sort {
|
||||
type string;
|
||||
}
|
||||
leaf where {
|
||||
type string;
|
||||
}
|
||||
}
|
||||
output {
|
||||
anyxml collection {
|
||||
description
|
||||
"Copy of the running datastore subset and/or state
|
||||
data that matched the filter criteria (if any).
|
||||
An empty data container indicates that the request did not
|
||||
produce any results.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue