Added patch media types; restconf patch test-cases; nsc spelling

This commit is contained in:
Olof hagsand 2019-08-09 14:49:40 +02:00
parent f319c18374
commit e244f5c8f8
12 changed files with 212 additions and 37 deletions

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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):"");

View file

@ -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;
} }

View file

@ -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));

View file

@ -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");

View file

@ -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 */

View file

@ -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);

View file

@ -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>

View file

@ -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"

View file

@ -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