Added patch media types; restconf patch test-cases; nsc spelling
This commit is contained in:
parent
f319c18374
commit
e244f5c8f8
12 changed files with 212 additions and 37 deletions
|
|
@ -42,8 +42,10 @@
|
||||||
* Types (also in restconf_lib.h)
|
* Types (also in restconf_lib.h)
|
||||||
*/
|
*/
|
||||||
enum restconf_media{
|
enum restconf_media{
|
||||||
YANG_DATA_JSON, /* "application/yang-data+json" (default for RESTCONF) */
|
YANG_DATA_JSON, /* "application/yang-data+json" */
|
||||||
YANG_DATA_XML /* "application/yang-data+xml" */
|
YANG_DATA_XML, /* "application/yang-data+xml" */
|
||||||
|
YANG_PATCH_JSON, /* "application/yang-patch+json" */
|
||||||
|
YANG_PATCH_XML /* "application/yang-patch+xml" */
|
||||||
};
|
};
|
||||||
typedef enum restconf_media restconf_media;
|
typedef enum restconf_media restconf_media;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@
|
||||||
***** END LICENSE BLOCK *****
|
***** END LICENSE BLOCK *****
|
||||||
|
|
||||||
* @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
|
* @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
|
||||||
|
* @note The response payload for errors uses text_html. RFC7231 is vague
|
||||||
|
* on the response payload (and its media). Maybe it should be omitted
|
||||||
|
* altogether?
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
@ -76,8 +79,8 @@ static const map_str2int netconf_restconf_map[] = {
|
||||||
{"bad-element", 400},
|
{"bad-element", 400},
|
||||||
{"unknown-element", 400},
|
{"unknown-element", 400},
|
||||||
{"unknown-namespace", 400},
|
{"unknown-namespace", 400},
|
||||||
{"access-denied", 401}, /* or 403 */
|
|
||||||
{"access-denied", 403},
|
{"access-denied", 403},
|
||||||
|
{"access-denied", 401}, /* or 403 */
|
||||||
{"lock-denied", 409},
|
{"lock-denied", 409},
|
||||||
{"resource-denied", 409},
|
{"resource-denied", 409},
|
||||||
{"rollback-failed", 500},
|
{"rollback-failed", 500},
|
||||||
|
|
@ -144,6 +147,8 @@ static const map_str2int http_reason_phrase_map[] = {
|
||||||
static const map_str2int http_media_map[] = {
|
static const map_str2int http_media_map[] = {
|
||||||
{"application/yang-data+xml", YANG_DATA_XML},
|
{"application/yang-data+xml", YANG_DATA_XML},
|
||||||
{"application/yang-data+json", YANG_DATA_JSON},
|
{"application/yang-data+json", YANG_DATA_JSON},
|
||||||
|
{"application/yang-patch+xml", YANG_PATCH_XML},
|
||||||
|
{"application/yang-patch+json", YANG_PATCH_JSON},
|
||||||
{NULL, -1}
|
{NULL, -1}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -172,6 +177,12 @@ restconf_media_int2str(restconf_media media)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Return media_in from Content-Type, -1 if not found or unrecognized
|
/*! Return media_in from Content-Type, -1 if not found or unrecognized
|
||||||
|
* @note media-type syntax does not support parameters
|
||||||
|
* @see RFC7231 Sec 3.1.1.1 for media-type syntax type:
|
||||||
|
* media-type = type "/" subtype *( OWS ";" OWS parameter )
|
||||||
|
* type = token
|
||||||
|
* subtype = token
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
restconf_media
|
restconf_media
|
||||||
restconf_content_type(FCGX_Request *r)
|
restconf_content_type(FCGX_Request *r)
|
||||||
|
|
@ -514,6 +525,10 @@ api_return_err(clicon_handle h,
|
||||||
FCGX_FPrintF(r->out, "%s", cbuf_get(cb));
|
FCGX_FPrintF(r->out, "%s", cbuf_get(cb));
|
||||||
FCGX_FPrintF(r->out, "}\r\n");
|
FCGX_FPrintF(r->out, "}\r\n");
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
clicon_err(OE_YANG, EINVAL, "Invalid media type %d", media);
|
||||||
|
goto done;
|
||||||
|
break;
|
||||||
} /* switch media */
|
} /* switch media */
|
||||||
ok:
|
ok:
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,13 @@
|
||||||
|
|
||||||
/*! RESTCONF media types
|
/*! RESTCONF media types
|
||||||
* @see http_media_map
|
* @see http_media_map
|
||||||
|
* (also in clixon_restconf.h)
|
||||||
*/
|
*/
|
||||||
enum restconf_media{
|
enum restconf_media{
|
||||||
YANG_DATA_JSON, /* "application/yang-data+json" (default for RESTCONF) */
|
YANG_DATA_JSON, /* "application/yang-data+json" */
|
||||||
YANG_DATA_XML /* "application/yang-data+xml" */
|
YANG_DATA_XML, /* "application/yang-data+xml" */
|
||||||
|
YANG_PATCH_JSON, /* "application/yang-patch+json" */
|
||||||
|
YANG_PATCH_XML /* "application/yang-patch+xml" */
|
||||||
};
|
};
|
||||||
typedef enum restconf_media restconf_media;
|
typedef enum restconf_media restconf_media;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -249,6 +249,8 @@ api_root(clicon_handle h,
|
||||||
if (xml2json_cbuf(cb, xt, pretty) < 0)
|
if (xml2json_cbuf(cb, xt, pretty) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
FCGX_FPrintF(r->out, "%s", cb?cbuf_get(cb):"");
|
FCGX_FPrintF(r->out, "%s", cb?cbuf_get(cb):"");
|
||||||
FCGX_FPrintF(r->out, "\r\n\r\n");
|
FCGX_FPrintF(r->out, "\r\n\r\n");
|
||||||
|
|
@ -297,6 +299,8 @@ api_yang_library_version(clicon_handle h,
|
||||||
if (xml2json_cbuf(cb, xt, pretty) < 0)
|
if (xml2json_cbuf(cb, xt, pretty) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
clicon_debug(1, "%s cb%s", __FUNCTION__, cbuf_get(cb));
|
clicon_debug(1, "%s cb%s", __FUNCTION__, cbuf_get(cb));
|
||||||
FCGX_FPrintF(r->out, "%s\n", cb?cbuf_get(cb):"");
|
FCGX_FPrintF(r->out, "%s\n", cb?cbuf_get(cb):"");
|
||||||
|
|
|
||||||
|
|
@ -510,7 +510,7 @@ api_data_write(clicon_handle h,
|
||||||
/* There is an api-path that defines an element in the datastore tree.
|
/* There is an api-path that defines an element in the datastore tree.
|
||||||
* Not top-of-tree.
|
* Not top-of-tree.
|
||||||
*/
|
*/
|
||||||
clicon_debug(1, "%s x:%s xbot:%s",__FUNCTION__, dname, xml_name(xbot));
|
clicon_debug(1, "%s Comparing bottom-of api-path (%s) with top-of-data (%s)",__FUNCTION__, xml_name(xbot), dname);
|
||||||
|
|
||||||
/* Check same symbol in api-path as data */
|
/* Check same symbol in api-path as data */
|
||||||
if (strcmp(dname, xml_name(xbot))){
|
if (strcmp(dname, xml_name(xbot))){
|
||||||
|
|
@ -683,7 +683,6 @@ api_data_write(clicon_handle h,
|
||||||
FCGX_SetExitStatus(204, r->out); /* Replaced */
|
FCGX_SetExitStatus(204, r->out); /* Replaced */
|
||||||
FCGX_FPrintF(r->out, "Status: 204 No Content\r\n");
|
FCGX_FPrintF(r->out, "Status: 204 No Content\r\n");
|
||||||
}
|
}
|
||||||
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
|
|
||||||
FCGX_FPrintF(r->out, "\r\n");
|
FCGX_FPrintF(r->out, "\r\n");
|
||||||
ok:
|
ok:
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
@ -794,13 +793,19 @@ api_data_patch(clicon_handle h,
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
media_in = restconf_content_type(r);
|
media_in = restconf_content_type(r);
|
||||||
if (media_in == YANG_DATA_XML || media_in == YANG_DATA_JSON){
|
switch (media_in){
|
||||||
/* plain patch */
|
case YANG_DATA_XML:
|
||||||
|
case YANG_DATA_JSON: /* plain patch */
|
||||||
ret = api_data_write(h, r, api_path0, pcvec, pi, qvec, data, pretty,
|
ret = api_data_write(h, r, api_path0, pcvec, pi, qvec, data, pretty,
|
||||||
media_in, media_out, 1);
|
media_in, media_out, 1);
|
||||||
}
|
break;
|
||||||
else{ /* Other patches are NYI */
|
case YANG_PATCH_XML:
|
||||||
|
case YANG_PATCH_JSON: /* RFC 8072 patch */
|
||||||
ret = restconf_notimplemented(r);
|
ret = restconf_notimplemented(r);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = restconf_unsupported_media(r);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,8 @@ api_data_get2(clicon_handle h,
|
||||||
if (xml2json_cbuf(cbx, xret, pretty) < 0)
|
if (xml2json_cbuf(cbx, xret, pretty) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
|
@ -244,6 +246,8 @@ api_data_get2(clicon_handle h,
|
||||||
if (xml2json_cbuf_vec(cbx, xvec, xlen, pretty) < 0)
|
if (xml2json_cbuf_vec(cbx, xvec, xlen, pretty) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx));
|
clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx));
|
||||||
|
|
@ -391,6 +395,8 @@ api_operations_get(clicon_handle h,
|
||||||
case YANG_DATA_JSON:
|
case YANG_DATA_JSON:
|
||||||
cprintf(cbx, "{\"operations\": {");
|
cprintf(cbx, "{\"operations\": {");
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
ymod = NULL;
|
ymod = NULL;
|
||||||
i = 0;
|
i = 0;
|
||||||
|
|
@ -409,7 +415,10 @@ api_operations_get(clicon_handle h,
|
||||||
cprintf(cbx, ",");
|
cprintf(cbx, ",");
|
||||||
cprintf(cbx, "\"%s:%s\": null", yang_argument_get(ymod), yang_argument_get(yc));
|
cprintf(cbx, "\"%s:%s\": null", yang_argument_get(ymod), yang_argument_get(yc));
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch (media_out){
|
switch (media_out){
|
||||||
|
|
@ -419,6 +428,8 @@ api_operations_get(clicon_handle h,
|
||||||
case YANG_DATA_JSON:
|
case YANG_DATA_JSON:
|
||||||
cprintf(cbx, "}}");
|
cprintf(cbx, "}}");
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
FCGX_SetExitStatus(200, r->out); /* OK */
|
FCGX_SetExitStatus(200, r->out); /* OK */
|
||||||
FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out));
|
FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out));
|
||||||
|
|
|
||||||
|
|
@ -951,6 +951,8 @@ api_operations_post(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
/* xoutput should now look: {"example:output": {"x":0,"y":42}} */
|
/* xoutput should now look: {"example:output": {"x":0,"y":42}} */
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
FCGX_FPrintF(r->out, "%s", cbuf_get(cbret));
|
FCGX_FPrintF(r->out, "%s", cbuf_get(cbret));
|
||||||
FCGX_FPrintF(r->out, "\r\n\r\n");
|
FCGX_FPrintF(r->out, "\r\n\r\n");
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,6 @@ int xml_nsctx_set(cvec *nsc, char *prefix, char *namespace);
|
||||||
cvec *xml_nsctx_init(char *prefix, char *namespace);
|
cvec *xml_nsctx_init(char *prefix, char *namespace);
|
||||||
int xml_nsctx_node(cxobj *x, cvec **ncp);
|
int xml_nsctx_node(cxobj *x, cvec **ncp);
|
||||||
int xml_nsctx_yang(yang_stmt *yn, cvec **ncp);
|
int xml_nsctx_yang(yang_stmt *yn, cvec **ncp);
|
||||||
int xml_nsctx_free(cvec *ncs);
|
int xml_nsctx_free(cvec *nsc);
|
||||||
|
|
||||||
#endif /* _CLIXON_XML_NSCTX_H */
|
#endif /* _CLIXON_XML_NSCTX_H */
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@ xml_nsctx_init(char *prefix,
|
||||||
|
|
||||||
static int
|
static int
|
||||||
xml_nsctx_node1(cxobj *xn,
|
xml_nsctx_node1(cxobj *xn,
|
||||||
cvec *ncs)
|
cvec *nsc)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
cxobj *xa = NULL;
|
cxobj *xa = NULL;
|
||||||
|
|
@ -186,30 +186,30 @@ xml_nsctx_node1(cxobj *xn,
|
||||||
nm = xml_name(xa);
|
nm = xml_name(xa);
|
||||||
if (pf == NULL){
|
if (pf == NULL){
|
||||||
if (strcmp(nm, "xmlns")==0 && /* set default namespace context */
|
if (strcmp(nm, "xmlns")==0 && /* set default namespace context */
|
||||||
xml_nsctx_get(ncs, NULL) == NULL){
|
xml_nsctx_get(nsc, NULL) == NULL){
|
||||||
val = xml_value(xa);
|
val = xml_value(xa);
|
||||||
if (xml_nsctx_set(ncs, NULL, val) < 0)
|
if (xml_nsctx_set(nsc, NULL, val) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
if (strcmp(pf, "xmlns")==0 && /* set prefixed namespace context */
|
if (strcmp(pf, "xmlns")==0 && /* set prefixed namespace context */
|
||||||
xml_nsctx_get(ncs, nm) == NULL){
|
xml_nsctx_get(nsc, nm) == NULL){
|
||||||
val = xml_value(xa);
|
val = xml_value(xa);
|
||||||
if (xml_nsctx_set(ncs, nm, val) < 0)
|
if (xml_nsctx_set(nsc, nm, val) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((xp = xml_parent(xn)) == NULL){
|
if ((xp = xml_parent(xn)) == NULL){
|
||||||
#ifdef USE_NETCONF_NS_AS_DEFAULT
|
#ifdef USE_NETCONF_NS_AS_DEFAULT
|
||||||
/* If not default namespace defined, use the base netconf ns as default */
|
/* If not default namespace defined, use the base netconf ns as default */
|
||||||
if (xml_nsctx_get(ncs, NULL) == NULL)
|
if (xml_nsctx_get(nsc, NULL) == NULL)
|
||||||
if (xml_nsctx_set(ncs, NULL, NETCONF_BASE_NAMESPACE) < 0)
|
if (xml_nsctx_set(nsc, NULL, NETCONF_BASE_NAMESPACE) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
if (xml_nsctx_node1(xp, ncs) < 0)
|
if (xml_nsctx_node1(xp, nsc) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
|
@ -345,9 +345,9 @@ xml_nsctx_yang(yang_stmt *yn,
|
||||||
* @retval NULL Error
|
* @retval NULL Error
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
xml_nsctx_free(cvec *ncs)
|
xml_nsctx_free(cvec *nsc)
|
||||||
{
|
{
|
||||||
cvec *cvv = (cvec*)ncs;
|
cvec *cvv = (cvec*)nsc;
|
||||||
|
|
||||||
if (cvv)
|
if (cvv)
|
||||||
cvec_free(cvv);
|
cvec_free(cvv);
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,6 @@ cat <<EOF > $cfg
|
||||||
<CLICON_MODULE_LIBRARY_RFC7895>false</CLICON_MODULE_LIBRARY_RFC7895>
|
<CLICON_MODULE_LIBRARY_RFC7895>false</CLICON_MODULE_LIBRARY_RFC7895>
|
||||||
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
|
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
|
||||||
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
||||||
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
|
|
||||||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||||
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
|
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ expecteq "$(curl -s -H 'Accept: application/yang-data+json' -G http://localhost/
|
||||||
'
|
'
|
||||||
|
|
||||||
new "restconf options. RFC 8040 4.1"
|
new "restconf options. RFC 8040 4.1"
|
||||||
expectpart "$(curl -is -X OPTIONS http://localhost/restconf/data)" 0 "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE"
|
expectpart "$(curl -is -X OPTIONS http://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE"
|
||||||
|
|
||||||
# -I means HEAD
|
# -I means HEAD
|
||||||
new "restconf HEAD. RFC 8040 4.2"
|
new "restconf HEAD. RFC 8040 4.2"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Restconf RFC8040 plain patch Sec 4.6 / 4.6.1
|
# Restconf RFC8040 plain patch Sec 4.6 / 4.6.1
|
||||||
|
# Use nacm module in example/main/example_restconf.c hardcoded to
|
||||||
|
# andy:bar and wilma:bar
|
||||||
|
|
||||||
# Magic line must be first in script (see README.md)
|
# Magic line must be first in script (see README.md)
|
||||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||||
|
|
@ -7,6 +9,7 @@ s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||||
APPNAME=example
|
APPNAME=example
|
||||||
|
|
||||||
cfg=$dir/conf.xml
|
cfg=$dir/conf.xml
|
||||||
|
startupdb=$dir/startup_db
|
||||||
fjukebox=$dir/example-jukebox.yang
|
fjukebox=$dir/example-jukebox.yang
|
||||||
|
|
||||||
cat <<EOF > $cfg
|
cat <<EOF > $cfg
|
||||||
|
|
@ -14,19 +17,82 @@ cat <<EOF > $cfg
|
||||||
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
|
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
|
||||||
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
|
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
|
||||||
<CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR>
|
<CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR>
|
||||||
<CLICON_YANG_MAIN_FILE>$fjukebox</CLICON_YANG_MAIN_FILE>
|
<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_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
|
||||||
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
|
<CLICON_FEATURE>ietf-netconf:startup</CLICON_FEATURE>
|
||||||
|
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||||
|
<CLICON_RESTCONF_DIR>/usr/local/lib/$APPNAME/restconf</CLICON_RESTCONF_DIR>
|
||||||
<CLICON_BACKEND_PIDFILE>$dir/restconf.pidfile</CLICON_BACKEND_PIDFILE>
|
<CLICON_BACKEND_PIDFILE>$dir/restconf.pidfile</CLICON_BACKEND_PIDFILE>
|
||||||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||||
|
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
|
||||||
|
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
|
||||||
</clixon-config>
|
</clixon-config>
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
cat<<EOF > $startupdb
|
||||||
|
<config>
|
||||||
|
<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
|
||||||
|
<enable-nacm>true</enable-nacm>
|
||||||
|
<read-default>deny</read-default>
|
||||||
|
<write-default>deny</write-default>
|
||||||
|
<exec-default>permit</exec-default>
|
||||||
|
<groups>
|
||||||
|
<group>
|
||||||
|
<name>admin</name>
|
||||||
|
<user-name>andy</user-name>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<name>limited</name>
|
||||||
|
<user-name>wilma</user-name>
|
||||||
|
</group>
|
||||||
|
</groups>
|
||||||
|
<rule-list>
|
||||||
|
<name>admin</name>
|
||||||
|
<group>admin</group>
|
||||||
|
<rule>
|
||||||
|
<name>permit-all</name>
|
||||||
|
<module-name>*</module-name>
|
||||||
|
<access-operations>*</access-operations>
|
||||||
|
<action>permit</action>
|
||||||
|
<comment>
|
||||||
|
Allow the 'admin' group complete access to all operations and data.
|
||||||
|
</comment>
|
||||||
|
</rule>
|
||||||
|
</rule-list>
|
||||||
|
<rule-list>
|
||||||
|
<name>limited</name>
|
||||||
|
<group>limited</group>
|
||||||
|
<rule>
|
||||||
|
<name>limit-jukebox</name>
|
||||||
|
<module-name>jukebox-example</module-name>
|
||||||
|
<access-operations>read create delete</access-operations>
|
||||||
|
<action>deny</action>
|
||||||
|
</rule>
|
||||||
|
</rule-list>
|
||||||
|
</nacm>
|
||||||
|
</config>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# An extra testmodule that includes nacm
|
||||||
|
cat <<EOF > $dir/example-system.yang
|
||||||
|
module example-system {
|
||||||
|
namespace "http://example.com/ns/example-system";
|
||||||
|
prefix "ex";
|
||||||
|
import ietf-netconf-acm {
|
||||||
|
prefix nacm;
|
||||||
|
}
|
||||||
|
container system {
|
||||||
|
leaf enable-jukebox-streaming {
|
||||||
|
type boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
# Common Jukebox spec (fjukebox must be set)
|
# Common Jukebox spec (fjukebox must be set)
|
||||||
. ./jukebox.sh
|
. ./jukebox.sh
|
||||||
|
|
||||||
new "test params: -f $cfg"
|
new "test params: -s startup -f $cfg"
|
||||||
|
|
||||||
if [ $BE -ne 0 ]; then
|
if [ $BE -ne 0 ]; then
|
||||||
new "kill old backend"
|
new "kill old backend"
|
||||||
|
|
@ -35,15 +101,15 @@ if [ $BE -ne 0 ]; then
|
||||||
err
|
err
|
||||||
fi
|
fi
|
||||||
sudo pkill clixon_backend # to be sure
|
sudo pkill clixon_backend # to be sure
|
||||||
new "start backend -s init -f $cfg"
|
new "start backend -s startup -f $cfg"
|
||||||
start_backend -s init -f $cfg
|
start_backend -s startup -f $cfg
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "kill old restconf daemon"
|
new "kill old restconf daemon"
|
||||||
sudo pkill -u www-data -f "/www-data/clixon_restconf"
|
sudo pkill -u www-data -f "/www-data/clixon_restconf"
|
||||||
|
|
||||||
new "start restconf daemon"
|
new "start restconf daemon (-a is enable basic authentication)"
|
||||||
start_restconf -f $cfg
|
start_restconf -f $cfg -- -a
|
||||||
|
|
||||||
new "waiting"
|
new "waiting"
|
||||||
wait_backend
|
wait_backend
|
||||||
|
|
@ -51,20 +117,88 @@ wait_restconf
|
||||||
|
|
||||||
# also in test_restconf.sh
|
# also in test_restconf.sh
|
||||||
new "MUST support the PATCH method for a plain patch"
|
new "MUST support the PATCH method for a plain patch"
|
||||||
expectpart "$(curl -is -X OPTIONS http://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE" "Accept-Patch: application/yang-data+xml,application/yang-data+json"
|
expectpart "$(curl -u andy:bar -is -X OPTIONS http://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE" "Accept-Patch: application/yang-data+xml,application/yang-data+json"
|
||||||
|
|
||||||
new "If the target resource instance does not exist, the server MUST NOT create it."
|
new "If the target resource instance does not exist, the server MUST NOT create it."
|
||||||
expectpart "$(curl -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox -d '{"example-jukebox:jukebox":null}')" 0 "HTTP/1.1 400 Bad Request"
|
expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox -d '{"example-jukebox:jukebox":null}')" 0 "HTTP/1.1 400 Bad Request"
|
||||||
|
|
||||||
new "Create it with PUT instead"
|
new "Create it with PUT instead"
|
||||||
expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox -d '{"example-jukebox:jukebox":null}')" 0 "HTTP/1.1 201 Created"
|
expectpart "$(curl -u andy:bar -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox -d '{"example-jukebox:jukebox":null}')" 0 "HTTP/1.1 201 Created"
|
||||||
|
|
||||||
new "THEN change it with PATCH"
|
new "THEN change it with PATCH"
|
||||||
expectpart "$(curl -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox -d '{"example-jukebox:jukebox":{"library":{"artist":{"name":"Clash"}}}}')" 0 "HTTP/1.1 204 No Content"
|
expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox -d '{"example-jukebox:jukebox":{"library":{"artist":{"name":"Clash"}}}}')" 0 "HTTP/1.1 204 No Content"
|
||||||
|
|
||||||
|
new "Check content (json)"
|
||||||
|
expectpart "$(curl -u andy:bar -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-jukebox:jukebox":{"library":{"artist":\[{"name":"Clash"}\]}}}'
|
||||||
|
|
||||||
|
new "Check content (xml)"
|
||||||
|
expectpart "$(curl -u andy:bar -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Clash</name></artist></library></jukebox>'
|
||||||
|
|
||||||
|
new 'If the user is not authorized, "403 Forbidden" SHOULD be returned.'
|
||||||
|
expectpart "$(curl -u wilma:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash -d '{"example-jukebox:artist":{"name":"Clash","album":{"name":"London Calling"}}}')" 0 "HTTP/1.1 403 Forbidden" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}'
|
||||||
|
|
||||||
|
new 'user is authorized'
|
||||||
|
expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash -d '{"example-jukebox:artist":{"name":"Clash","album":{"name":"London Calling"}}}')" 0 "HTTP/1.1 204 No Content"
|
||||||
|
|
||||||
|
# 4.6.1. Plain Patch
|
||||||
|
|
||||||
|
new "restconf DELETE whole datastore"
|
||||||
|
expectpart "$(curl -u andy:bar -is -X DELETE http://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content"
|
||||||
|
|
||||||
|
new "Create album London Calling with PUT"
|
||||||
|
expectpart "$(curl -u andy:bar -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":{"name":"London Calling"}}')" 0 "HTTP/1.1 201 Created"
|
||||||
|
|
||||||
|
new "The message-body for a plain patch MUST be present"
|
||||||
|
expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Beatles -d '')" 0 "HTTP/1.1 400 Bad Request"
|
||||||
|
|
||||||
|
# Plain patch can be used to create or update, but not delete, a child
|
||||||
|
# resource within the target resource.
|
||||||
|
new "Create a child resource (genre and year)"
|
||||||
|
expectpart "$(curl -u andy:bar -si -X PATCH -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":{"name":"London Calling","genre":"example-jukebox:rock","year":"2129"}}')" 0 'HTTP/1.1 204 No Content'
|
||||||
|
|
||||||
|
new "Update a child resource (year)"
|
||||||
|
expectpart "$(curl -u andy:bar -si -X PATCH -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":{"name":"London Calling","year":"1979"}}')" 0 'HTTP/1.1 204 No Content'
|
||||||
|
|
||||||
|
new "Check content xml"
|
||||||
|
expectpart "$(curl -u andy:bar -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<album xmlns="http://example.com/ns/example-jukebox"><name>London Calling</name><genre>rock</genre><year>1979</year></album>'
|
||||||
|
|
||||||
|
new "Check content json"
|
||||||
|
expectpart "$(curl -u andy:bar -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-jukebox:album":\[{"name":"London Calling","genre":"rock","year":1979}\]}'
|
||||||
|
|
||||||
|
new "The message-body MUST be represented by the media type application/yang-data+xml (or +json ^)"
|
||||||
|
expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '<album xmlns="http://example.com/ns/example-jukebox"><name>London Calling</name><genre>jazz</genre></album>')" 0 "HTTP/1.1 204 No Content"
|
||||||
|
|
||||||
|
new "Check content (xml)"
|
||||||
|
expectpart "$(curl -u andy:bar -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Clash</name><album><name>London Calling</name><genre>jazz</genre><year>1979</year></album></artist></library></jukebox>'
|
||||||
|
|
||||||
|
new "not implemented media type"
|
||||||
|
expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-patch+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '<album xmlns="http://example.com/ns/example-jukebox"><name>London Calling</name><genre>jazz</genre></album>')" 0 "HTTP/1.1 501 Not Implemented"
|
||||||
|
|
||||||
|
new "wrong media type"
|
||||||
|
expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: text/html' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '<album xmlns="http://example.com/ns/example-jukebox"><name>London Calling</name><genre>jazz</genre></album>')" 0 "HTTP/1.1 415 Unsupported Media Type"
|
||||||
|
|
||||||
|
# If the target resource represents a YANG leaf-list, then the PATCH
|
||||||
|
# method MUST NOT change the value of the leaf-list instance.
|
||||||
|
# leaf-list extra{
|
||||||
|
new "Create leaf-list a"
|
||||||
|
expectpart "$(curl -u andy:bar -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:extra=a -d '{"example-jukebox:extra":"a"}')" 0 "HTTP/1.1 201 Created"
|
||||||
|
|
||||||
|
new "Create leaf-list b"
|
||||||
|
expectpart "$(curl -u andy:bar -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:extra=b -d '{"example-jukebox:extra":"b"}')" 0 "HTTP/1.1 201 Created"
|
||||||
|
|
||||||
new "Check content"
|
new "Check content"
|
||||||
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-jukebox:jukebox":{"library":{"artist":\[{"name":"Clash"}\]}}}'
|
expectpart "$(curl -u andy:bar -si -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-jukebox:extra":\["a","b"\]}'
|
||||||
|
|
||||||
|
new "MUST NOT change the value of the leaf-list instance"
|
||||||
|
expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:extra=a -d '{"example-jukebox:extra":"b"}')" 0 'HTTP/1.1 412 Precondition Failed'
|
||||||
|
|
||||||
|
# If the target resource represents a YANG list instance, then the key
|
||||||
|
# leaf values, in message-body representation, MUST be the same as the
|
||||||
|
# key leaf values in the request URI. The PATCH method MUST NOT be
|
||||||
|
# used to change the key leaf values for a data resource instance.
|
||||||
|
|
||||||
|
new "The key leaf values MUST be the same as the key leaf values in the request"
|
||||||
|
expectpart "$(curl -u andy:bar -si -X PATCH -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":{"name":"The Clash"}}')" 0 'HTTP/1.1 412 Precondition Failed'
|
||||||
|
|
||||||
new "Kill restconf daemon"
|
new "Kill restconf daemon"
|
||||||
stop_restconf
|
stop_restconf
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue