RESTCONF PUT/POST -d {} media is enforced
This commit is contained in:
parent
aa653d0831
commit
aa14f8ac2c
35 changed files with 933 additions and 640 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -29,6 +29,8 @@
|
||||||
* RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns:
|
* RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns:
|
||||||
* `201 Created` for created resources
|
* `201 Created` for created resources
|
||||||
* `204 No Content` for replaced resources.
|
* `204 No Content` for replaced resources.
|
||||||
|
* RESTCONF PUT/POST -d {} media is enforced
|
||||||
|
* Before accepted JSON as default, now Content-Type must be explicit, such as `Content-Type: application/yang-data+json`
|
||||||
* RESTCONF identities has been changed to use module names instead of prefixes.
|
* RESTCONF identities has been changed to use module names instead of prefixes.
|
||||||
* Eg, `curl -X POST -d '{"type":"ex:eth"}` --> `curl -X POST -d '{"type":"ietf-interfaces:eth"`}
|
* Eg, `curl -X POST -d '{"type":"ex:eth"}` --> `curl -X POST -d '{"type":"ietf-interfaces:eth"`}
|
||||||
* JSON changes
|
* JSON changes
|
||||||
|
|
@ -45,6 +47,15 @@
|
||||||
* pseudo-plugin added, to enable callbacks also for main programs. Useful for extensions
|
* pseudo-plugin added, to enable callbacks also for main programs. Useful for extensions
|
||||||
|
|
||||||
### Corrected Bugs
|
### Corrected Bugs
|
||||||
|
* Corrected CLI bug with lists of multiple keys (netconf/restconf works).
|
||||||
|
* Worked in 3.10, but broke in 4.0
|
||||||
|
* Example: `yang list x { key "a b";...}`
|
||||||
|
CLI error example:
|
||||||
|
```
|
||||||
|
set x a 1 b 1; #OK
|
||||||
|
set x a 1 b 2; #OK
|
||||||
|
set x a 1 b <anything> # Error
|
||||||
|
```
|
||||||
* Fixed RESTCONF api-path leaf-list selection was not made properly
|
* Fixed RESTCONF api-path leaf-list selection was not made properly
|
||||||
* Requesting eg `mod:x/y=42` returned the whole list: `{"y":[41,42,43]}` whereas it should only return one element: `{"y":42}`
|
* Requesting eg `mod:x/y=42` returned the whole list: `{"y":[41,42,43]}` whereas it should only return one element: `{"y":42}`
|
||||||
* See [RESTCONF: HTTP return codes are not according to RFC 8040](https://github.com/clicon/clixon/issues/56)
|
* See [RESTCONF: HTTP return codes are not according to RFC 8040](https://github.com/clicon/clixon/issues/56)
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,15 @@
|
||||||
#ifndef _CLIXON_RESTCONF_H_
|
#ifndef _CLIXON_RESTCONF_H_
|
||||||
#define _CLIXON_RESTCONF_H_
|
#define _CLIXON_RESTCONF_H_
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Types (also in restconf_lib.h)
|
||||||
|
*/
|
||||||
|
enum restconf_media{
|
||||||
|
YANG_DATA_JSON, /* "application/yang-data+json" (default for RESTCONF) */
|
||||||
|
YANG_DATA_XML /* "application/yang-data+xml" */
|
||||||
|
};
|
||||||
|
typedef enum restconf_media restconf_media;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prototypes (also in restconf_lib.h)
|
* Prototypes (also in restconf_lib.h)
|
||||||
*/
|
*/
|
||||||
|
|
@ -55,7 +64,7 @@ int restconf_test(FCGX_Request *r, int dbg);
|
||||||
cbuf *readdata(FCGX_Request *r);
|
cbuf *readdata(FCGX_Request *r);
|
||||||
int get_user_cookie(char *cookiestr, char *attribute, char **val);
|
int get_user_cookie(char *cookiestr, char *attribute, char **val);
|
||||||
int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr,
|
int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr,
|
||||||
int pretty, int use_xml, int code);
|
int pretty, restconf_media media, int code);
|
||||||
|
|
||||||
|
|
||||||
#endif /* _CLIXON_RESTCONF_H_ */
|
#endif /* _CLIXON_RESTCONF_H_ */
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,14 @@ static const map_str2int http_reason_phrase_map[] = {
|
||||||
{NULL, -1}
|
{NULL, -1}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* See RFC 8040
|
||||||
|
*/
|
||||||
|
static const map_str2int http_media_map[] = {
|
||||||
|
{"application/yang-data+xml", YANG_DATA_XML},
|
||||||
|
{"application/yang-data+json", YANG_DATA_JSON},
|
||||||
|
{NULL, -1}
|
||||||
|
};
|
||||||
|
|
||||||
int
|
int
|
||||||
restconf_err2code(char *tag)
|
restconf_err2code(char *tag)
|
||||||
{
|
{
|
||||||
|
|
@ -151,11 +159,23 @@ restconf_code2reason(int code)
|
||||||
return clicon_int2str(http_reason_phrase_map, code);
|
return clicon_int2str(http_reason_phrase_map, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const restconf_media
|
||||||
|
restconf_media_str2int(char *media)
|
||||||
|
{
|
||||||
|
return clicon_str2int(http_media_map, media);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
restconf_media_int2str(restconf_media media)
|
||||||
|
{
|
||||||
|
return clicon_int2str(http_media_map, media);
|
||||||
|
}
|
||||||
|
|
||||||
/*! HTTP error 400
|
/*! HTTP error 400
|
||||||
* @param[in] r Fastcgi request handle
|
* @param[in] r Fastcgi request handle
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
badrequest(FCGX_Request *r)
|
restconf_badrequest(FCGX_Request *r)
|
||||||
{
|
{
|
||||||
char *path;
|
char *path;
|
||||||
|
|
||||||
|
|
@ -173,7 +193,7 @@ badrequest(FCGX_Request *r)
|
||||||
* @param[in] r Fastcgi request handle
|
* @param[in] r Fastcgi request handle
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
unauthorized(FCGX_Request *r)
|
restconf_unauthorized(FCGX_Request *r)
|
||||||
{
|
{
|
||||||
char *path;
|
char *path;
|
||||||
|
|
||||||
|
|
@ -190,7 +210,7 @@ unauthorized(FCGX_Request *r)
|
||||||
* @param[in] r Fastcgi request handle
|
* @param[in] r Fastcgi request handle
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
forbidden(FCGX_Request *r)
|
restconf_forbidden(FCGX_Request *r)
|
||||||
{
|
{
|
||||||
char *path;
|
char *path;
|
||||||
|
|
||||||
|
|
@ -207,7 +227,7 @@ forbidden(FCGX_Request *r)
|
||||||
* @param[in] r Fastcgi request handle
|
* @param[in] r Fastcgi request handle
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
notfound(FCGX_Request *r)
|
restconf_notfound(FCGX_Request *r)
|
||||||
{
|
{
|
||||||
char *path;
|
char *path;
|
||||||
|
|
||||||
|
|
@ -227,7 +247,7 @@ notfound(FCGX_Request *r)
|
||||||
* @param[in] r Fastcgi request handle
|
* @param[in] r Fastcgi request handle
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
notacceptable(FCGX_Request *r)
|
restconf_notacceptable(FCGX_Request *r)
|
||||||
{
|
{
|
||||||
char *path;
|
char *path;
|
||||||
|
|
||||||
|
|
@ -247,7 +267,7 @@ notacceptable(FCGX_Request *r)
|
||||||
* @param[in] r Fastcgi request handle
|
* @param[in] r Fastcgi request handle
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
conflict(FCGX_Request *r)
|
restconf_conflict(FCGX_Request *r)
|
||||||
{
|
{
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
FCGX_FPrintF(r->out, "Status: 409\r\n"); /* 409 Conflict */
|
FCGX_FPrintF(r->out, "Status: 409\r\n"); /* 409 Conflict */
|
||||||
|
|
@ -256,11 +276,25 @@ conflict(FCGX_Request *r)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! HTTP error 409
|
||||||
|
* @param[in] r Fastcgi request handle
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
restconf_unsupported_media(FCGX_Request *r)
|
||||||
|
{
|
||||||
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
|
FCGX_SetExitStatus(415, r->out);
|
||||||
|
FCGX_FPrintF(r->out, "Status: 415 Unsupported Media Type\r\n");
|
||||||
|
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
|
||||||
|
FCGX_FPrintF(r->out, "<h1>Unsupported Media Type</h1>\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*! HTTP error 500
|
/*! HTTP error 500
|
||||||
* @param[in] r Fastcgi request handle
|
* @param[in] r Fastcgi request handle
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
internal_server_error(FCGX_Request *r)
|
restconf_internal_server_error(FCGX_Request *r)
|
||||||
{
|
{
|
||||||
char *path;
|
char *path;
|
||||||
|
|
||||||
|
|
@ -276,7 +310,7 @@ internal_server_error(FCGX_Request *r)
|
||||||
* @param[in] r Fastcgi request handle
|
* @param[in] r Fastcgi request handle
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
notimplemented(FCGX_Request *r)
|
restconf_notimplemented(FCGX_Request *r)
|
||||||
{
|
{
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
FCGX_FPrintF(r->out, "Status: 501\r\n");
|
FCGX_FPrintF(r->out, "Status: 501\r\n");
|
||||||
|
|
@ -285,7 +319,6 @@ notimplemented(FCGX_Request *r)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @param[in] r Fastcgi request handle
|
* @param[in] r Fastcgi request handle
|
||||||
*/
|
*/
|
||||||
|
|
@ -396,16 +429,16 @@ get_user_cookie(char *cookiestr,
|
||||||
* @param[in] r Fastcgi request handle
|
* @param[in] r Fastcgi request handle
|
||||||
* @param[in] xerr XML error message from backend
|
* @param[in] xerr XML error message from backend
|
||||||
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||||
* @param[in] use_xml Set to 0 for JSON and 1 for XML
|
* @param[in] media Output media
|
||||||
* @param[in] code If 0 use rfc8040 sec 7 netconf2restconf error-tag mapping
|
* @param[in] code If 0 use rfc8040 sec 7 netconf2restconf error-tag mapping
|
||||||
* otherwise use this code
|
* otherwise use this code
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
api_return_err(clicon_handle h,
|
api_return_err(clicon_handle h,
|
||||||
FCGX_Request *r,
|
FCGX_Request *r,
|
||||||
cxobj *xerr,
|
cxobj *xerr,
|
||||||
int pretty,
|
int pretty,
|
||||||
int use_xml,
|
restconf_media media,
|
||||||
int code0)
|
int code0)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
|
|
@ -419,7 +452,7 @@ api_return_err(clicon_handle h,
|
||||||
if ((cb = cbuf_new()) == NULL)
|
if ((cb = cbuf_new()) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
if ((xtag = xpath_first(xerr, "//error-tag")) == NULL){
|
if ((xtag = xpath_first(xerr, "//error-tag")) == NULL){
|
||||||
notfound(r);
|
restconf_notfound(r);
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
tagstr = xml_body(xtag);
|
tagstr = xml_body(xtag);
|
||||||
|
|
@ -433,20 +466,14 @@ api_return_err(clicon_handle h,
|
||||||
reason_phrase="";
|
reason_phrase="";
|
||||||
if (xml_name_set(xerr, "error") < 0)
|
if (xml_name_set(xerr, "error") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (use_xml){
|
|
||||||
if (clicon_xml2cbuf(cb, xerr, 2, pretty) < 0)
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if (xml2json_cbuf(cb, xerr, pretty) < 0)
|
|
||||||
goto done;
|
|
||||||
clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb));
|
|
||||||
FCGX_SetExitStatus(code, r->out); /* Created */
|
FCGX_SetExitStatus(code, r->out); /* Created */
|
||||||
FCGX_FPrintF(r->out, "Status: %d %s\r\n", code, reason_phrase);
|
FCGX_FPrintF(r->out, "Status: %d %s\r\n", code, reason_phrase);
|
||||||
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n\r\n",
|
FCGX_FPrintF(r->out, "Content-Type: %s\r\n\r\n", restconf_media_int2str(media));
|
||||||
use_xml?"xml":"json");
|
switch (media){
|
||||||
|
case YANG_DATA_XML:
|
||||||
if (use_xml){
|
if (clicon_xml2cbuf(cb, xerr, 2, pretty) < 0)
|
||||||
|
goto done;
|
||||||
|
clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb));
|
||||||
if (pretty){
|
if (pretty){
|
||||||
FCGX_FPrintF(r->out, " <errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">\n", cbuf_get(cb));
|
FCGX_FPrintF(r->out, " <errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">\n", cbuf_get(cb));
|
||||||
FCGX_FPrintF(r->out, "%s", cbuf_get(cb));
|
FCGX_FPrintF(r->out, "%s", cbuf_get(cb));
|
||||||
|
|
@ -457,8 +484,11 @@ 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, "</errors>\r\n");
|
FCGX_FPrintF(r->out, "</errors>\r\n");
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
else{
|
case YANG_DATA_JSON:
|
||||||
|
if (xml2json_cbuf(cb, xerr, pretty) < 0)
|
||||||
|
goto done;
|
||||||
|
clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb));
|
||||||
if (pretty){
|
if (pretty){
|
||||||
FCGX_FPrintF(r->out, "{\n");
|
FCGX_FPrintF(r->out, "{\n");
|
||||||
FCGX_FPrintF(r->out, " \"ietf-restconf:errors\" : %s\n",
|
FCGX_FPrintF(r->out, " \"ietf-restconf:errors\" : %s\n",
|
||||||
|
|
@ -471,7 +501,7 @@ 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");
|
||||||
}
|
}
|
||||||
}
|
} /* switch media */
|
||||||
ok:
|
ok:
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
|
@ -683,3 +713,4 @@ restconf_uripath(FCGX_Request *r)
|
||||||
*q = '\0';
|
*q = '\0';
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,26 +41,43 @@
|
||||||
*/
|
*/
|
||||||
#define RESTCONF_API "restconf"
|
#define RESTCONF_API "restconf"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Types
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! RESTCONF media types
|
||||||
|
* @see http_media_map
|
||||||
|
*/
|
||||||
|
enum restconf_media{
|
||||||
|
YANG_DATA_JSON, /* "application/yang-data+json" (default for RESTCONF) */
|
||||||
|
YANG_DATA_XML /* "application/yang-data+xml" */
|
||||||
|
};
|
||||||
|
typedef enum restconf_media restconf_media;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prototypes (also in clixon_restconf.h)
|
* Prototypes (also in clixon_restconf.h)
|
||||||
*/
|
*/
|
||||||
int restconf_err2code(char *tag);
|
int restconf_err2code(char *tag);
|
||||||
const char *restconf_code2reason(int code);
|
const char *restconf_code2reason(int code);
|
||||||
|
|
||||||
int badrequest(FCGX_Request *r);
|
const restconf_media restconf_media_str2int(char *media);
|
||||||
int unauthorized(FCGX_Request *r);
|
const char *restconf_media_int2str(restconf_media media);
|
||||||
int forbidden(FCGX_Request *r);
|
|
||||||
int notfound(FCGX_Request *r);
|
int restconf_badrequest(FCGX_Request *r);
|
||||||
int notacceptable(FCGX_Request *r);
|
int restconf_unauthorized(FCGX_Request *r);
|
||||||
int conflict(FCGX_Request *r);
|
int restconf_forbidden(FCGX_Request *r);
|
||||||
int internal_server_error(FCGX_Request *r);
|
int restconf_notfound(FCGX_Request *r);
|
||||||
int notimplemented(FCGX_Request *r);
|
int restconf_notacceptable(FCGX_Request *r);
|
||||||
|
int restconf_conflict(FCGX_Request *r);
|
||||||
|
int restconf_unsupported_media(FCGX_Request *r);
|
||||||
|
int restconf_internal_server_error(FCGX_Request *r);
|
||||||
|
int restconf_notimplemented(FCGX_Request *r);
|
||||||
|
|
||||||
int restconf_test(FCGX_Request *r, int dbg);
|
int restconf_test(FCGX_Request *r, int dbg);
|
||||||
cbuf *readdata(FCGX_Request *r);
|
cbuf *readdata(FCGX_Request *r);
|
||||||
int get_user_cookie(char *cookiestr, char *attribute, char **val);
|
int get_user_cookie(char *cookiestr, char *attribute, char **val);
|
||||||
int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr,
|
int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr,
|
||||||
int pretty, int use_xml, int code);
|
int pretty, enum restconf_media media, int code);
|
||||||
int http_location(FCGX_Request *r, cxobj *xobj);
|
int http_location(FCGX_Request *r, cxobj *xobj);
|
||||||
int restconf_terminate(clicon_handle h);
|
int restconf_terminate(clicon_handle h);
|
||||||
int restconf_insert_attributes(cxobj *xdata, cvec *qvec);
|
int restconf_insert_attributes(cxobj *xdata, cvec *qvec);
|
||||||
|
|
|
||||||
|
|
@ -101,8 +101,8 @@
|
||||||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||||
* @param[in] dvec Stream input daat
|
* @param[in] dvec Stream input daat
|
||||||
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||||
* @param[in] use_xml Set to 0 for JSON and 1 for XML
|
* @param[in] media_in Input media
|
||||||
* @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data
|
* @param[in] media_out Output media
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
api_data(clicon_handle h,
|
api_data(clicon_handle h,
|
||||||
|
|
@ -113,8 +113,8 @@ api_data(clicon_handle h,
|
||||||
cvec *qvec,
|
cvec *qvec,
|
||||||
char *data,
|
char *data,
|
||||||
int pretty,
|
int pretty,
|
||||||
int use_xml,
|
restconf_media media_in,
|
||||||
int parse_xml)
|
restconf_media media_out)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
char *request_method;
|
char *request_method;
|
||||||
|
|
@ -125,19 +125,19 @@ api_data(clicon_handle h,
|
||||||
if (strcmp(request_method, "OPTIONS")==0)
|
if (strcmp(request_method, "OPTIONS")==0)
|
||||||
retval = api_data_options(h, r);
|
retval = api_data_options(h, r);
|
||||||
else if (strcmp(request_method, "HEAD")==0)
|
else if (strcmp(request_method, "HEAD")==0)
|
||||||
retval = api_data_head(h, r, pcvec, pi, qvec, pretty, use_xml);
|
retval = api_data_head(h, r, pcvec, pi, qvec, pretty, media_out);
|
||||||
else if (strcmp(request_method, "GET")==0)
|
else if (strcmp(request_method, "GET")==0)
|
||||||
retval = api_data_get(h, r, pcvec, pi, qvec, pretty, use_xml);
|
retval = api_data_get(h, r, pcvec, pi, qvec, pretty, media_out);
|
||||||
else if (strcmp(request_method, "POST")==0)
|
else if (strcmp(request_method, "POST")==0)
|
||||||
retval = api_data_post(h, r, api_path, pcvec, pi, qvec, data, pretty, use_xml, parse_xml);
|
retval = api_data_post(h, r, api_path, pcvec, pi, qvec, data, pretty, media_in, media_out);
|
||||||
else if (strcmp(request_method, "PUT")==0)
|
else if (strcmp(request_method, "PUT")==0)
|
||||||
retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data, pretty, use_xml, parse_xml);
|
retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data, pretty, media_in, media_out);
|
||||||
else if (strcmp(request_method, "PATCH")==0)
|
else if (strcmp(request_method, "PATCH")==0)
|
||||||
retval = api_data_patch(h, r, api_path, pcvec, pi, qvec, data);
|
retval = api_data_patch(h, r, api_path, pcvec, pi, qvec, data, pretty, media_in, media_out);
|
||||||
else if (strcmp(request_method, "DELETE")==0)
|
else if (strcmp(request_method, "DELETE")==0)
|
||||||
retval = api_data_delete(h, r, api_path, pi, pretty, use_xml);
|
retval = api_data_delete(h, r, api_path, pi, pretty, media_out);
|
||||||
else
|
else
|
||||||
retval = notfound(r);
|
retval = restconf_notfound(r);
|
||||||
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
@ -150,7 +150,8 @@ api_data(clicon_handle h,
|
||||||
* @param[in] pi Offset, where to start pcvec
|
* @param[in] pi Offset, where to start pcvec
|
||||||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||||
* @param[in] data Stream input data
|
* @param[in] data Stream input data
|
||||||
* @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data
|
* @param[in] media_in Input media media
|
||||||
|
* @param[in] media_out Output media
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
api_operations(clicon_handle h,
|
api_operations(clicon_handle h,
|
||||||
|
|
@ -161,8 +162,8 @@ api_operations(clicon_handle h,
|
||||||
cvec *qvec,
|
cvec *qvec,
|
||||||
char *data,
|
char *data,
|
||||||
int pretty,
|
int pretty,
|
||||||
int use_xml,
|
restconf_media media_in,
|
||||||
int parse_xml)
|
restconf_media media_out)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
char *request_method;
|
char *request_method;
|
||||||
|
|
@ -171,12 +172,12 @@ api_operations(clicon_handle h,
|
||||||
request_method = FCGX_GetParam("REQUEST_METHOD", r->envp);
|
request_method = FCGX_GetParam("REQUEST_METHOD", r->envp);
|
||||||
clicon_debug(1, "%s method:%s", __FUNCTION__, request_method);
|
clicon_debug(1, "%s method:%s", __FUNCTION__, request_method);
|
||||||
if (strcmp(request_method, "GET")==0)
|
if (strcmp(request_method, "GET")==0)
|
||||||
retval = api_operations_get(h, r, path, pcvec, pi, qvec, data, pretty, use_xml);
|
retval = api_operations_get(h, r, path, pcvec, pi, qvec, data, pretty, media_out);
|
||||||
else if (strcmp(request_method, "POST")==0)
|
else if (strcmp(request_method, "POST")==0)
|
||||||
retval = api_operations_post(h, r, path, pcvec, pi, qvec, data,
|
retval = api_operations_post(h, r, path, pcvec, pi, qvec, data,
|
||||||
pretty, use_xml, parse_xml);
|
pretty, media_in, media_out);
|
||||||
else
|
else
|
||||||
retval = notfound(r);
|
retval = restconf_notfound(r);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,15 +212,15 @@ api_well_known(clicon_handle h,
|
||||||
* See RFC8040 3.3
|
* See RFC8040 3.3
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
api_root(clicon_handle h,
|
api_root(clicon_handle h,
|
||||||
FCGX_Request *r)
|
FCGX_Request *r,
|
||||||
|
int pretty,
|
||||||
|
restconf_media media_out)
|
||||||
|
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
char *media_accept;
|
|
||||||
int use_xml = 0; /* By default use JSON */
|
|
||||||
cxobj *xt = NULL;
|
cxobj *xt = NULL;
|
||||||
cbuf *cb = NULL;
|
cbuf *cb = NULL;
|
||||||
int pretty;
|
|
||||||
yang_stmt *yspec;
|
yang_stmt *yspec;
|
||||||
|
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
|
|
@ -227,14 +228,10 @@ api_root(clicon_handle h,
|
||||||
clicon_err(OE_FATAL, 0, "No DB_SPEC");
|
clicon_err(OE_FATAL, 0, "No DB_SPEC");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
|
|
||||||
media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp);
|
|
||||||
if (strcmp(media_accept, "application/yang-data+xml")==0)
|
|
||||||
use_xml++;
|
|
||||||
clicon_debug(1, "%s use-xml:%d media-accept:%s", __FUNCTION__, use_xml, media_accept);
|
|
||||||
FCGX_SetExitStatus(200, r->out); /* OK */
|
FCGX_SetExitStatus(200, r->out); /* OK */
|
||||||
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
|
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
|
||||||
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
|
|
||||||
|
FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out));
|
||||||
FCGX_FPrintF(r->out, "\r\n");
|
FCGX_FPrintF(r->out, "\r\n");
|
||||||
|
|
||||||
if (xml_parse_string("<restconf xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><data/><operations/><yang-library-version>2016-06-21</yang-library-version></restconf>", NULL, &xt) < 0)
|
if (xml_parse_string("<restconf xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><data/><operations/><yang-library-version>2016-06-21</yang-library-version></restconf>", NULL, &xt) < 0)
|
||||||
|
|
@ -247,13 +244,16 @@ api_root(clicon_handle h,
|
||||||
}
|
}
|
||||||
if (xml_rootchild(xt, 0, &xt) < 0)
|
if (xml_rootchild(xt, 0, &xt) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (use_xml){
|
switch (media_out){
|
||||||
|
case YANG_DATA_XML:
|
||||||
if (clicon_xml2cbuf(cb, xt, 0, pretty) < 0)
|
if (clicon_xml2cbuf(cb, xt, 0, pretty) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
break;
|
||||||
else
|
case YANG_DATA_JSON:
|
||||||
if (xml2json_cbuf(cb, xt, pretty) < 0)
|
if (xml2json_cbuf(cb, xt, pretty) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
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");
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
@ -270,24 +270,20 @@ api_root(clicon_handle h,
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
api_yang_library_version(clicon_handle h,
|
api_yang_library_version(clicon_handle h,
|
||||||
FCGX_Request *r)
|
FCGX_Request *r,
|
||||||
|
int pretty,
|
||||||
|
restconf_media media_out)
|
||||||
|
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
char *media_accept;
|
|
||||||
int use_xml = 0; /* By default use JSON */
|
|
||||||
cxobj *xt = NULL;
|
cxobj *xt = NULL;
|
||||||
cbuf *cb = NULL;
|
cbuf *cb = NULL;
|
||||||
int pretty;
|
|
||||||
char *ietf_yang_library_revision = "2016-06-21"; /* XXX */
|
char *ietf_yang_library_revision = "2016-06-21"; /* XXX */
|
||||||
|
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
|
|
||||||
media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp);
|
|
||||||
if (strcmp(media_accept, "application/yang-data+xml")==0)
|
|
||||||
use_xml++;
|
|
||||||
FCGX_SetExitStatus(200, r->out); /* OK */
|
FCGX_SetExitStatus(200, r->out); /* OK */
|
||||||
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
|
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
|
||||||
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
|
FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out));
|
||||||
FCGX_FPrintF(r->out, "\r\n");
|
FCGX_FPrintF(r->out, "\r\n");
|
||||||
if (xml_parse_va(&xt, NULL, "<yang-library-version>%s</yang-library-version>", ietf_yang_library_revision) < 0)
|
if (xml_parse_va(&xt, NULL, "<yang-library-version>%s</yang-library-version>", ietf_yang_library_revision) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -296,13 +292,15 @@ api_yang_library_version(clicon_handle h,
|
||||||
if ((cb = cbuf_new()) == NULL){
|
if ((cb = cbuf_new()) == NULL){
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (use_xml){
|
switch (media_out){
|
||||||
|
case YANG_DATA_XML:
|
||||||
if (clicon_xml2cbuf(cb, xt, 0, pretty) < 0)
|
if (clicon_xml2cbuf(cb, xt, 0, pretty) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
break;
|
||||||
else{
|
case YANG_DATA_JSON:
|
||||||
if (xml2json_cbuf(cb, xt, pretty) < 0)
|
if (xml2json_cbuf(cb, xt, pretty) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
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):"");
|
||||||
|
|
@ -335,11 +333,10 @@ api_restconf(clicon_handle h,
|
||||||
cbuf *cb = NULL;
|
cbuf *cb = NULL;
|
||||||
char *data;
|
char *data;
|
||||||
int authenticated = 0;
|
int authenticated = 0;
|
||||||
char *media_accept;
|
char *media_str = NULL;
|
||||||
char *media_content_type;
|
restconf_media media_in = YANG_DATA_JSON; /* XXX defaults should be set in methods, here is too generic */
|
||||||
|
restconf_media media_out = YANG_DATA_JSON;
|
||||||
int pretty;
|
int pretty;
|
||||||
int parse_xml = 0; /* By default expect and parse JSON */
|
|
||||||
int use_xml = 0; /* By default use JSON */
|
|
||||||
cbuf *cbret = NULL;
|
cbuf *cbret = NULL;
|
||||||
cxobj *xret = NULL;
|
cxobj *xret = NULL;
|
||||||
cxobj *xerr;
|
cxobj *xerr;
|
||||||
|
|
@ -348,37 +345,61 @@ api_restconf(clicon_handle h,
|
||||||
path = restconf_uripath(r);
|
path = restconf_uripath(r);
|
||||||
query = FCGX_GetParam("QUERY_STRING", r->envp);
|
query = FCGX_GetParam("QUERY_STRING", r->envp);
|
||||||
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
|
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
|
||||||
/* get xml/json in put and output */
|
/* Get media for input (Content-Type)
|
||||||
media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp);
|
* This is for methods that have input, such as PUT/POST, etc
|
||||||
if (media_accept && strcmp(media_accept, "application/yang-data+xml")==0)
|
*/
|
||||||
use_xml++;
|
if ((media_str = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp)) == NULL){
|
||||||
media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp);
|
retval = restconf_unsupported_media(r);
|
||||||
if (media_content_type &&
|
goto done;
|
||||||
strcmp(media_content_type, "application/yang-data+xml")==0)
|
}
|
||||||
parse_xml++;
|
else if ((media_in = restconf_media_str2int(media_str)) == -1){
|
||||||
|
clicon_debug(1, "%s Content_Type: %s (unsupported)", __FUNCTION__, media_str);
|
||||||
|
retval = restconf_unsupported_media(r);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
clicon_debug(1, "%s CONTENT_TYPE: %s %s", __FUNCTION__, media_str, restconf_media_int2str(media_in));
|
||||||
|
/* Get media for output (proactive negotiation) RFC7231 by using
|
||||||
|
* Accept:. This is for methods that have output, such as GET,
|
||||||
|
* operation POST, etc
|
||||||
|
* If accept is * default is yang-json
|
||||||
|
*/
|
||||||
|
if ((media_str = FCGX_GetParam("HTTP_ACCEPT", r->envp)) == NULL){
|
||||||
|
// retval = restconf_unsupported_media(r);
|
||||||
|
// goto done;
|
||||||
|
}
|
||||||
|
else if ((media_out = restconf_media_str2int(media_str)) == -1){
|
||||||
|
if (strcmp(media_str, "*/*") == 0) /* catch-all */
|
||||||
|
media_out = YANG_DATA_JSON;
|
||||||
|
else{
|
||||||
|
retval = restconf_unsupported_media(r);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clicon_debug(1, "%s ACCEPT: %s %s", __FUNCTION__, media_str, restconf_media_int2str(media_out));
|
||||||
|
|
||||||
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
|
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
/* Sanity check of path. Should be /restconf/ */
|
/* Sanity check of path. Should be /restconf/ */
|
||||||
if (pn < 2){
|
if (pn < 2){
|
||||||
notfound(r);
|
restconf_notfound(r);
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
if (strlen(pvec[0]) != 0){
|
if (strlen(pvec[0]) != 0){
|
||||||
retval = notfound(r);
|
retval = restconf_notfound(r);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (strcmp(pvec[1], RESTCONF_API)){
|
if (strcmp(pvec[1], RESTCONF_API)){
|
||||||
retval = notfound(r);
|
retval = restconf_notfound(r);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
restconf_test(r, 1);
|
restconf_test(r, 1);
|
||||||
|
|
||||||
if (pn == 2){
|
if (pn == 2){
|
||||||
retval = api_root(h, r);
|
retval = api_root(h, r, pretty, media_out);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if ((method = pvec[2]) == NULL){
|
if ((method = pvec[2]) == NULL){
|
||||||
retval = notfound(r);
|
retval = restconf_notfound(r);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
clicon_debug(1, "%s: method=%s", __FUNCTION__, method);
|
clicon_debug(1, "%s: method=%s", __FUNCTION__, method);
|
||||||
|
|
@ -410,7 +431,7 @@ api_restconf(clicon_handle h,
|
||||||
if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0)
|
if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
|
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
|
||||||
if (api_return_err(h, r, xerr, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xerr, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -418,23 +439,23 @@ api_restconf(clicon_handle h,
|
||||||
}
|
}
|
||||||
clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
|
clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
|
||||||
if (strcmp(method, "yang-library-version")==0){
|
if (strcmp(method, "yang-library-version")==0){
|
||||||
if (api_yang_library_version(h, r) < 0)
|
if (api_yang_library_version(h, r, pretty, media_out) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
else if (strcmp(method, "data") == 0){ /* restconf, skip /api/data */
|
else if (strcmp(method, "data") == 0){ /* restconf, skip /api/data */
|
||||||
if (api_data(h, r, path, pcvec, 2, qvec, data,
|
if (api_data(h, r, path, pcvec, 2, qvec, data,
|
||||||
pretty, use_xml, parse_xml) < 0)
|
pretty, media_in, media_out) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
else if (strcmp(method, "operations") == 0){ /* rpc */
|
else if (strcmp(method, "operations") == 0){ /* rpc */
|
||||||
if (api_operations(h, r, path, pcvec, 2, qvec, data,
|
if (api_operations(h, r, path, pcvec, 2, qvec, data,
|
||||||
pretty, use_xml, parse_xml) < 0)
|
pretty, media_in, media_out) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
else if (strcmp(method, "test") == 0)
|
else if (strcmp(method, "test") == 0)
|
||||||
restconf_test(r, 0);
|
restconf_test(r, 0);
|
||||||
else
|
else
|
||||||
notfound(r);
|
restconf_notfound(r);
|
||||||
ok:
|
ok:
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
|
@ -796,7 +817,7 @@ main(int argc,
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
clicon_debug(1, "top-level %s not found", path);
|
clicon_debug(1, "top-level %s not found", path);
|
||||||
notfound(r);
|
restconf_notfound(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -224,8 +224,8 @@ match_list_keys(yang_stmt *y,
|
||||||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||||
* @param[in] data Stream input data
|
* @param[in] data Stream input data
|
||||||
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||||
* @param[in] use_xml Set to 0 for JSON and 1 for XML for output data
|
* @param[in] media_in Input media media
|
||||||
* @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data
|
* @param[in] media_out Output media
|
||||||
|
|
||||||
* @note restconf PUT is mapped to edit-config replace.
|
* @note restconf PUT is mapped to edit-config replace.
|
||||||
* @see RFC8040 Sec 4.5 PUT
|
* @see RFC8040 Sec 4.5 PUT
|
||||||
|
|
@ -260,8 +260,8 @@ api_data_put(clicon_handle h,
|
||||||
cvec *qvec,
|
cvec *qvec,
|
||||||
char *data,
|
char *data,
|
||||||
int pretty,
|
int pretty,
|
||||||
int use_xml,
|
restconf_media media_in,
|
||||||
int parse_xml)
|
restconf_media media_out)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
enum operation_type op;
|
enum operation_type op;
|
||||||
|
|
@ -302,13 +302,13 @@ api_data_put(clicon_handle h,
|
||||||
/* Create config top-of-tree */
|
/* Create config top-of-tree */
|
||||||
if ((xtop = xml_new("config", NULL, NULL)) == NULL)
|
if ((xtop = xml_new("config", NULL, NULL)) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
/* Translate api_path to xtop/xbot */
|
/* Translate api_path to xml in the form of xtop/xbot */
|
||||||
xbot = xtop;
|
xbot = xtop;
|
||||||
if (api_path){
|
if (api_path){ /* If URI, otherwise top data/config object */
|
||||||
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &ybot)) < 0)
|
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &ybot)) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (ybot)
|
if (ybot)
|
||||||
ymodapi=ys_module(ybot);
|
ymodapi = ys_module(ybot);
|
||||||
if (ret == 0){ /* validation failed */
|
if (ret == 0){ /* validation failed */
|
||||||
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -317,14 +317,15 @@ api_data_put(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Parse input data as json or xml into xml */
|
/* Parse input data as json or xml into xml */
|
||||||
if (parse_xml){
|
switch (media_in){
|
||||||
|
case YANG_DATA_XML:
|
||||||
if (xml_parse_string(data, yspec, &xdata0) < 0){
|
if (xml_parse_string(data, yspec, &xdata0) < 0){
|
||||||
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -332,12 +333,12 @@ api_data_put(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
else{
|
case YANG_DATA_JSON:
|
||||||
/* Data here cannot cannot be Yang populated since it is loosely
|
/* Data here cannot cannot be Yang populated since it is loosely
|
||||||
* hanging without top symbols.
|
* hanging without top symbols.
|
||||||
* And if it is not yang populated, it cant be translated properly
|
* And if it is not yang populated, it cant be translated properly
|
||||||
|
|
@ -351,7 +352,7 @@ api_data_put(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -360,11 +361,11 @@ api_data_put(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
}
|
} /* switch media_in */
|
||||||
|
|
||||||
/* The message-body MUST contain exactly one instance of the
|
/* The message-body MUST contain exactly one instance of the
|
||||||
* expected data resource.
|
* expected data resource.
|
||||||
|
|
@ -376,7 +377,7 @@ api_data_put(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -404,7 +405,7 @@ api_data_put(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -445,7 +446,7 @@ api_data_put(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -470,7 +471,7 @@ api_data_put(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -494,7 +495,7 @@ api_data_put(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -509,7 +510,7 @@ api_data_put(clicon_handle h,
|
||||||
/* xbot is already populated, resolve yang for added xdata too */
|
/* xbot is already populated, resolve yang for added xdata too */
|
||||||
if (xml_apply0(xdata, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
if (xml_apply0(xdata, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (!parse_xml && nullspec){
|
if (media_in == YANG_DATA_JSON && nullspec){
|
||||||
/* json2xml decode could not be done above in json_parse,
|
/* json2xml decode could not be done above in json_parse,
|
||||||
* need to be done here instead
|
* need to be done here instead
|
||||||
* UNLESS it is root resource, then json-parse has already done it
|
* UNLESS it is root resource, then json-parse has already done it
|
||||||
|
|
@ -517,7 +518,7 @@ api_data_put(clicon_handle h,
|
||||||
if ((ret = json2xml_decode(xdata, &xerr)) < 0)
|
if ((ret = json2xml_decode(xdata, &xerr)) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (ret == 0){
|
if (ret == 0){
|
||||||
if (api_return_err(h, r, xerr, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xerr, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -561,7 +562,7 @@ api_data_put(clicon_handle h,
|
||||||
*/
|
*/
|
||||||
if (xpath_first(xe, ".[error-tag=\"data-exists\"]") == NULL ||
|
if (xpath_first(xe, ".[error-tag=\"data-exists\"]") == NULL ||
|
||||||
op == OP_REPLACE){
|
op == OP_REPLACE){
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -598,7 +599,7 @@ api_data_put(clicon_handle h,
|
||||||
/* log errors from discard, but ignore */
|
/* log errors from discard, but ignore */
|
||||||
if ((xpath_first(xretdis, "//rpc-error")) != NULL)
|
if ((xpath_first(xretdis, "//rpc-error")) != NULL)
|
||||||
clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__);
|
clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__);
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -656,14 +657,13 @@ api_data_put(clicon_handle h,
|
||||||
return retval;
|
return retval;
|
||||||
} /* api_data_put */
|
} /* api_data_put */
|
||||||
|
|
||||||
|
|
||||||
/*! Generic REST DELETE method translated to edit-config
|
/*! Generic REST DELETE method translated to edit-config
|
||||||
* @param[in] h CLIXON handle
|
* @param[in] h CLIXON handle
|
||||||
* @param[in] r Fastcgi request handle
|
* @param[in] r Fastcgi request handle
|
||||||
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
|
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
|
||||||
* @param[in] pi Offset, where path starts
|
* @param[in] pi Offset, where path starts
|
||||||
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||||
* @param[in] use_xml Set to 0 for JSON and 1 for XML
|
* @param[in] media_out Output media
|
||||||
* See RFC 8040 Sec 4.7
|
* See RFC 8040 Sec 4.7
|
||||||
* Example:
|
* Example:
|
||||||
* curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0
|
* curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0
|
||||||
|
|
@ -675,7 +675,7 @@ api_data_delete(clicon_handle h,
|
||||||
char *api_path,
|
char *api_path,
|
||||||
int pi,
|
int pi,
|
||||||
int pretty,
|
int pretty,
|
||||||
int use_xml)
|
restconf_media media_out)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
int i;
|
int i;
|
||||||
|
|
@ -716,7 +716,7 @@ api_data_delete(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -744,7 +744,7 @@ api_data_delete(clicon_handle h,
|
||||||
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0)
|
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if ((xe = xpath_first(xret, "//rpc-error")) != NULL){
|
if ((xe = xpath_first(xret, "//rpc-error")) != NULL){
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -766,7 +766,7 @@ api_data_delete(clicon_handle h,
|
||||||
/* log errors from discard, but ignore */
|
/* log errors from discard, but ignore */
|
||||||
if ((xpath_first(xretdis, "//rpc-error")) != NULL)
|
if ((xpath_first(xretdis, "//rpc-error")) != NULL)
|
||||||
clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__);
|
clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__);
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,9 @@ int api_data_options(clicon_handle h, FCGX_Request *r);
|
||||||
int api_data_put(clicon_handle h, FCGX_Request *r, char *api_path,
|
int api_data_put(clicon_handle h, FCGX_Request *r, char *api_path,
|
||||||
cvec *pcvec, int pi,
|
cvec *pcvec, int pi,
|
||||||
cvec *qvec, char *data,
|
cvec *qvec, char *data,
|
||||||
int pretty, int use_xml, int parse_xml);
|
int pretty,
|
||||||
|
restconf_media media_in, restconf_media media_out);
|
||||||
int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi,
|
int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi,
|
||||||
int pretty, int use_xml);
|
int pretty, restconf_media media_out);
|
||||||
|
|
||||||
#endif /* _RESTCONF_METHODS_H_ */
|
#endif /* _RESTCONF_METHODS_H_ */
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@
|
||||||
* @param[in] pi Offset, where path starts
|
* @param[in] pi Offset, where path starts
|
||||||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||||
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||||
* @param[in] use_xml Set to 0 for JSON and 1 for XML
|
* @param[in] media_out Output media
|
||||||
* @param[in] head If 1 is HEAD, otherwise GET
|
* @param[in] head If 1 is HEAD, otherwise GET
|
||||||
* @code
|
* @code
|
||||||
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
|
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
|
||||||
|
|
@ -96,7 +96,7 @@ api_data_get2(clicon_handle h,
|
||||||
int pi,
|
int pi,
|
||||||
cvec *qvec,
|
cvec *qvec,
|
||||||
int pretty,
|
int pretty,
|
||||||
int use_xml,
|
restconf_media media_out,
|
||||||
int head)
|
int head)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
|
|
@ -131,7 +131,7 @@ api_data_get2(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +148,7 @@ api_data_get2(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -167,7 +167,7 @@ api_data_get2(clicon_handle h,
|
||||||
#endif
|
#endif
|
||||||
/* Check if error return */
|
/* Check if error return */
|
||||||
if ((xe = xpath_first(xret, "//rpc-error")) != NULL){
|
if ((xe = xpath_first(xret, "//rpc-error")) != NULL){
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -176,28 +176,20 @@ api_data_get2(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
if (head){
|
if (head){
|
||||||
FCGX_SetExitStatus(200, r->out); /* OK */
|
FCGX_SetExitStatus(200, r->out); /* OK */
|
||||||
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
|
FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out));
|
||||||
FCGX_FPrintF(r->out, "\r\n");
|
FCGX_FPrintF(r->out, "\r\n");
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
if (xpath==NULL || strcmp(xpath,"/")==0){ /* Special case: data root */
|
if (xpath==NULL || strcmp(xpath,"/")==0){ /* Special case: data root */
|
||||||
if (use_xml){
|
switch (media_out){
|
||||||
|
case YANG_DATA_XML:
|
||||||
if (clicon_xml2cbuf(cbx, xret, 0, pretty) < 0) /* Dont print top object? */
|
if (clicon_xml2cbuf(cbx, xret, 0, pretty) < 0) /* Dont print top object? */
|
||||||
goto done;
|
goto done;
|
||||||
}
|
break;
|
||||||
else{
|
case YANG_DATA_JSON:
|
||||||
#if 0
|
if (xml2json_cbuf(cbx, xret, pretty) < 0)
|
||||||
if (debug){
|
goto done;
|
||||||
cbuf *ccc=cbuf_new();
|
break;
|
||||||
if (clicon_xml2cbuf(ccc, xret, 0, 0) < 0)
|
|
||||||
goto done;
|
|
||||||
clicon_debug(1, "%s xret: %s",
|
|
||||||
__FUNCTION__, cbuf_get(ccc));
|
|
||||||
cbuf_free(ccc);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (xml2json_cbuf(cbx, xret, pretty) < 0)
|
|
||||||
goto done;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
|
@ -208,7 +200,7 @@ api_data_get2(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -221,11 +213,12 @@ api_data_get2(clicon_handle h,
|
||||||
if (netconf_invalid_value_xml(&xerr, "application", "Instance does not exist") < 0)
|
if (netconf_invalid_value_xml(&xerr, "application", "Instance does not exist") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
/* override invalid-value default 400 with 404 */
|
/* override invalid-value default 400 with 404 */
|
||||||
if (api_return_err(h, r, xerr, pretty, use_xml, 404) < 0)
|
if (api_return_err(h, r, xerr, pretty, media_out, 404) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
if (use_xml){
|
switch (media_out){
|
||||||
|
case YANG_DATA_XML:
|
||||||
for (i=0; i<xlen; i++){
|
for (i=0; i<xlen; i++){
|
||||||
char *prefix, *namespace2; /* Same as namespace? */
|
char *prefix, *namespace2; /* Same as namespace? */
|
||||||
x = xvec[i];
|
x = xvec[i];
|
||||||
|
|
@ -240,19 +233,20 @@ api_data_get2(clicon_handle h,
|
||||||
if (clicon_xml2cbuf(cbx, x, 0, pretty) < 0) /* Dont print top object? */
|
if (clicon_xml2cbuf(cbx, x, 0, pretty) < 0) /* Dont print top object? */
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
else{
|
case YANG_DATA_JSON:
|
||||||
/* In: <x xmlns="urn:example:clixon">0</x>
|
/* In: <x xmlns="urn:example:clixon">0</x>
|
||||||
* Out: {"example:x": {"0"}}
|
* Out: {"example:x": {"0"}}
|
||||||
*/
|
*/
|
||||||
if (xml2json_cbuf_vec(cbx, xvec, xlen, pretty) < 0)
|
if (xml2json_cbuf_vec(cbx, xvec, xlen, pretty) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx));
|
clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx));
|
||||||
FCGX_SetExitStatus(200, r->out); /* OK */
|
FCGX_SetExitStatus(200, r->out); /* OK */
|
||||||
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
|
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
|
||||||
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
|
FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out));
|
||||||
FCGX_FPrintF(r->out, "\r\n");
|
FCGX_FPrintF(r->out, "\r\n");
|
||||||
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
|
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
|
||||||
FCGX_FPrintF(r->out, "\r\n\r\n");
|
FCGX_FPrintF(r->out, "\r\n\r\n");
|
||||||
|
|
@ -282,7 +276,7 @@ api_data_get2(clicon_handle h,
|
||||||
* @param[in] pi Offset, where path starts
|
* @param[in] pi Offset, where path starts
|
||||||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||||
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||||
* @param[in] use_xml Set to 0 for JSON and 1 for XML
|
* @param[in] media_out Output media
|
||||||
*
|
*
|
||||||
* The HEAD method is sent by the client to retrieve just the header fields
|
* The HEAD method is sent by the client to retrieve just the header fields
|
||||||
* that would be returned for the comparable GET method, without the
|
* that would be returned for the comparable GET method, without the
|
||||||
|
|
@ -296,9 +290,9 @@ api_data_head(clicon_handle h,
|
||||||
int pi,
|
int pi,
|
||||||
cvec *qvec,
|
cvec *qvec,
|
||||||
int pretty,
|
int pretty,
|
||||||
int use_xml)
|
restconf_media media_out)
|
||||||
{
|
{
|
||||||
return api_data_get2(h, r, pcvec, pi, qvec, pretty, use_xml, 1);
|
return api_data_get2(h, r, pcvec, pi, qvec, pretty, media_out, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! REST GET method
|
/*! REST GET method
|
||||||
|
|
@ -309,7 +303,7 @@ api_data_head(clicon_handle h,
|
||||||
* @param[in] pi Offset, where path starts
|
* @param[in] pi Offset, where path starts
|
||||||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||||
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||||
* @param[in] use_xml Set to 0 for JSON and 1 for XML
|
* @param[in] media_out Output media
|
||||||
* @code
|
* @code
|
||||||
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
|
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
|
||||||
* @endcode
|
* @endcode
|
||||||
|
|
@ -333,9 +327,9 @@ api_data_get(clicon_handle h,
|
||||||
int pi,
|
int pi,
|
||||||
cvec *qvec,
|
cvec *qvec,
|
||||||
int pretty,
|
int pretty,
|
||||||
int use_xml)
|
restconf_media media_out)
|
||||||
{
|
{
|
||||||
return api_data_get2(h, r, pcvec, pi, qvec, pretty, use_xml, 0);
|
return api_data_get2(h, r, pcvec, pi, qvec, pretty, media_out, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! GET restconf/operations resource
|
/*! GET restconf/operations resource
|
||||||
|
|
@ -347,7 +341,7 @@ api_data_get(clicon_handle h,
|
||||||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||||
* @param[in] data Stream input data
|
* @param[in] data Stream input data
|
||||||
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||||
* @param[in] use_xml Set to 0 for JSON and 1 for XML
|
* @param[in] media_out Output media
|
||||||
*
|
*
|
||||||
* @code
|
* @code
|
||||||
* curl -G http://localhost/restconf/operations
|
* curl -G http://localhost/restconf/operations
|
||||||
|
|
@ -372,7 +366,7 @@ api_operations_get(clicon_handle h,
|
||||||
cvec *qvec,
|
cvec *qvec,
|
||||||
char *data,
|
char *data,
|
||||||
int pretty,
|
int pretty,
|
||||||
int use_xml)
|
restconf_media media_out)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
yang_stmt *yspec;
|
yang_stmt *yspec;
|
||||||
|
|
@ -387,10 +381,14 @@ api_operations_get(clicon_handle h,
|
||||||
yspec = clicon_dbspec_yang(h);
|
yspec = clicon_dbspec_yang(h);
|
||||||
if ((cbx = cbuf_new()) == NULL)
|
if ((cbx = cbuf_new()) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
if (use_xml)
|
switch (media_out){
|
||||||
|
case YANG_DATA_XML:
|
||||||
cprintf(cbx, "<operations>");
|
cprintf(cbx, "<operations>");
|
||||||
else
|
break;
|
||||||
|
case YANG_DATA_JSON:
|
||||||
cprintf(cbx, "{\"operations\": {");
|
cprintf(cbx, "{\"operations\": {");
|
||||||
|
break;
|
||||||
|
}
|
||||||
ymod = NULL;
|
ymod = NULL;
|
||||||
i = 0;
|
i = 0;
|
||||||
while ((ymod = yn_each(yspec, ymod)) != NULL) {
|
while ((ymod = yn_each(yspec, ymod)) != NULL) {
|
||||||
|
|
@ -399,21 +397,28 @@ api_operations_get(clicon_handle h,
|
||||||
while ((yc = yn_each(ymod, yc)) != NULL) {
|
while ((yc = yn_each(ymod, yc)) != NULL) {
|
||||||
if (yang_keyword_get(yc) != Y_RPC)
|
if (yang_keyword_get(yc) != Y_RPC)
|
||||||
continue;
|
continue;
|
||||||
if (use_xml)
|
switch (media_out){
|
||||||
|
case YANG_DATA_XML:
|
||||||
cprintf(cbx, "<%s xmlns=\"%s\"/>", yang_argument_get(yc), namespace);
|
cprintf(cbx, "<%s xmlns=\"%s\"/>", yang_argument_get(yc), namespace);
|
||||||
else{
|
break;
|
||||||
|
case YANG_DATA_JSON:
|
||||||
if (i++)
|
if (i++)
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (use_xml)
|
switch (media_out){
|
||||||
|
case YANG_DATA_XML:
|
||||||
cprintf(cbx, "</operations>");
|
cprintf(cbx, "</operations>");
|
||||||
else
|
break;
|
||||||
|
case YANG_DATA_JSON:
|
||||||
cprintf(cbx, "}}");
|
cprintf(cbx, "}}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
FCGX_SetExitStatus(200, r->out); /* OK */
|
FCGX_SetExitStatus(200, r->out); /* OK */
|
||||||
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
|
FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out));
|
||||||
FCGX_FPrintF(r->out, "\r\n");
|
FCGX_FPrintF(r->out, "\r\n");
|
||||||
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
|
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
|
||||||
FCGX_FPrintF(r->out, "\r\n\r\n");
|
FCGX_FPrintF(r->out, "\r\n\r\n");
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@
|
||||||
* Restconf method implementation for operations get and data get and head
|
* Restconf method implementation for operations get and data get and head
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#ifndef _RESTCONF_METHODS_GET_H_
|
#ifndef _RESTCONF_METHODS_GET_H_
|
||||||
#define _RESTCONF_METHODS_GET_H_
|
#define _RESTCONF_METHODS_GET_H_
|
||||||
|
|
||||||
|
|
@ -42,12 +41,12 @@
|
||||||
* Prototypes
|
* Prototypes
|
||||||
*/
|
*/
|
||||||
int api_data_head(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi,
|
int api_data_head(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi,
|
||||||
cvec *qvec, int pretty, int use_xml);
|
cvec *qvec, int pretty, restconf_media media_out);
|
||||||
int api_data_get(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi,
|
int api_data_get(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi,
|
||||||
cvec *qvec, int pretty, int use_xml);
|
cvec *qvec, int pretty, restconf_media media_out);
|
||||||
int api_operations_get(clicon_handle h, FCGX_Request *r,
|
int api_operations_get(clicon_handle h, FCGX_Request *r,
|
||||||
char *path,
|
char *path,
|
||||||
cvec *pcvec, int pi, cvec *qvec, char *data,
|
cvec *pcvec, int pi, cvec *qvec, char *data,
|
||||||
int pretty, int use_xml);
|
int pretty, restconf_media media_out);
|
||||||
|
|
||||||
#endif /* _RESTCONF_METHODS_GET_H_ */
|
#endif /* _RESTCONF_METHODS_GET_H_ */
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ Mapping netconf error-tag -> status code
|
||||||
#include "restconf_methods_patch.h"
|
#include "restconf_methods_patch.h"
|
||||||
|
|
||||||
|
|
||||||
/*! Generic REST PATCH method
|
/*! Generic REST PATCH method for plain patch
|
||||||
* @param[in] h CLIXON handle
|
* @param[in] h CLIXON handle
|
||||||
* @param[in] r Fastcgi request handle
|
* @param[in] r Fastcgi request handle
|
||||||
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
|
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
|
||||||
|
|
@ -133,19 +133,117 @@ Mapping netconf error-tag -> status code
|
||||||
* @param[in] pi Offset, where to start pcvec
|
* @param[in] pi Offset, where to start pcvec
|
||||||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||||
* @param[in] data Stream input data
|
* @param[in] data Stream input data
|
||||||
|
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||||
|
* @param[in] media_out Output media
|
||||||
* Netconf: <edit-config> (nc:operation="merge")
|
* Netconf: <edit-config> (nc:operation="merge")
|
||||||
* See RFC8040 Sec 4.6
|
* See RFC8040 Sec 4.6.1
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
api_data_patch(clicon_handle h,
|
api_data_patch(clicon_handle h,
|
||||||
FCGX_Request *r,
|
FCGX_Request *r,
|
||||||
char *api_path,
|
char *api_path0,
|
||||||
cvec *pcvec,
|
cvec *pcvec,
|
||||||
int pi,
|
int pi,
|
||||||
cvec *qvec,
|
cvec *qvec,
|
||||||
char *data)
|
char *data,
|
||||||
|
int pretty,
|
||||||
|
restconf_media media_in,
|
||||||
|
restconf_media media_out)
|
||||||
{
|
{
|
||||||
notimplemented(r);
|
int retval = -1;
|
||||||
return 0;
|
yang_stmt *yspec;
|
||||||
|
// yang_stmt *ymodapi = NULL; /* yang module of api-path (if any) */
|
||||||
|
cxobj *xdata0 = NULL; /* Original -d data struct (including top symbol) */
|
||||||
|
int i;
|
||||||
|
char *api_path;
|
||||||
|
cxobj *xtop = NULL; /* top of api-path */
|
||||||
|
cxobj *xbot = NULL; /* bottom of api-path */
|
||||||
|
yang_stmt *ybot = NULL; /* yang of xbot */
|
||||||
|
cxobj *xerr = NULL; /* malloced must be freed */
|
||||||
|
cxobj *xe;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path0);
|
||||||
|
clicon_debug(1, "%s data:\"%s\"", __FUNCTION__, data);
|
||||||
|
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||||
|
clicon_err(OE_FATAL, 0, "No DB_SPEC");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
api_path=api_path0;
|
||||||
|
for (i=0; i<pi; i++)
|
||||||
|
api_path = index(api_path+1, '/');
|
||||||
|
/* Create config top-of-tree */
|
||||||
|
if ((xtop = xml_new("config", NULL, NULL)) == NULL)
|
||||||
|
goto done;
|
||||||
|
/* Translate api_path to xml in the form of xtop/xbot */
|
||||||
|
xbot = xtop;
|
||||||
|
if (api_path){ /* If URI, otherwise top data/config object */
|
||||||
|
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &ybot)) < 0)
|
||||||
|
goto done;
|
||||||
|
// if (ybot)
|
||||||
|
// ymodapi = ys_module(ybot);
|
||||||
|
if (ret == 0){ /* validation failed */
|
||||||
|
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
||||||
|
goto done;
|
||||||
|
clicon_err_reset();
|
||||||
|
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
|
||||||
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
|
goto done;
|
||||||
|
goto ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Parse input data as json or xml into xml */
|
||||||
|
switch (media_in){
|
||||||
|
case YANG_DATA_XML:
|
||||||
|
if (xml_parse_string(data, yspec, &xdata0) < 0){
|
||||||
|
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
||||||
|
goto done;
|
||||||
|
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
|
||||||
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
|
goto done;
|
||||||
|
goto ok;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case YANG_DATA_JSON:
|
||||||
|
/* Data here cannot cannot be Yang populated since it is loosely
|
||||||
|
* hanging without top symbols.
|
||||||
|
* And if it is not yang populated, it cant be translated properly
|
||||||
|
* from JSON to XML.
|
||||||
|
* Therefore, yang population is done later after addsub below
|
||||||
|
*/
|
||||||
|
if ((ret = json_parse_str(data, yspec, &xdata0, &xerr)) < 0){
|
||||||
|
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
||||||
|
goto done;
|
||||||
|
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
|
||||||
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
|
goto done;
|
||||||
|
goto ok;
|
||||||
|
}
|
||||||
|
if (ret == 0){
|
||||||
|
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
|
||||||
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
|
goto done;
|
||||||
|
goto ok;
|
||||||
|
}
|
||||||
|
} /* switch media_in */
|
||||||
|
restconf_notimplemented(r);
|
||||||
|
ok:
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
if (xtop)
|
||||||
|
xml_free(xtop);
|
||||||
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@
|
||||||
*/
|
*/
|
||||||
int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path,
|
int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path,
|
||||||
cvec *pcvec, int pi,
|
cvec *pcvec, int pi,
|
||||||
cvec *qvec, char *data);
|
cvec *qvec, char *data, int pretty,
|
||||||
|
restconf_media media_in, restconf_media media_out);
|
||||||
|
|
||||||
#endif /* _RESTCONF_METHODS_PATCH_H_ */
|
#endif /* _RESTCONF_METHODS_PATCH_H_ */
|
||||||
|
|
|
||||||
|
|
@ -73,9 +73,8 @@
|
||||||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||||
* @param[in] data Stream input data
|
* @param[in] data Stream input data
|
||||||
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||||
* @param[in] use_xml Set to 0 for JSON and 1 for XML for output data
|
* @param[in] media_in Input media
|
||||||
* @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data
|
* @param[in] media_out Output media
|
||||||
|
|
||||||
* restconf POST is mapped to edit-config create.
|
* restconf POST is mapped to edit-config create.
|
||||||
* @see RFC8040 Sec 4.4.1
|
* @see RFC8040 Sec 4.4.1
|
||||||
|
|
||||||
|
|
@ -107,8 +106,8 @@ api_data_post(clicon_handle h,
|
||||||
cvec *qvec,
|
cvec *qvec,
|
||||||
char *data,
|
char *data,
|
||||||
int pretty,
|
int pretty,
|
||||||
int use_xml,
|
restconf_media media_in,
|
||||||
int parse_xml)
|
restconf_media media_out)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
enum operation_type op = OP_CREATE;
|
enum operation_type op = OP_CREATE;
|
||||||
|
|
@ -158,7 +157,7 @@ api_data_post(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -173,7 +172,8 @@ api_data_post(clicon_handle h,
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
/* Parse input data as json or xml into xml */
|
/* Parse input data as json or xml into xml */
|
||||||
if (parse_xml){
|
switch (media_in){
|
||||||
|
case YANG_DATA_XML:
|
||||||
if (xml_parse_string(data, NULL, &xdata0) < 0){
|
if (xml_parse_string(data, NULL, &xdata0) < 0){
|
||||||
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -181,12 +181,12 @@ api_data_post(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
else {
|
case YANG_DATA_JSON:
|
||||||
/* Data here cannot cannot (always) be Yang populated since it is
|
/* Data here cannot cannot (always) be Yang populated since it is
|
||||||
* loosely hanging without top symbols.
|
* loosely hanging without top symbols.
|
||||||
* And if it is not yang populated, it cant be translated properly
|
* And if it is not yang populated, it cant be translated properly
|
||||||
|
|
@ -203,7 +203,7 @@ api_data_post(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -212,11 +212,12 @@ api_data_post(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
|
} /* switch media_in */
|
||||||
/* 4.4.1: The message-body MUST contain exactly one instance of the
|
/* 4.4.1: The message-body MUST contain exactly one instance of the
|
||||||
* expected data resource.
|
* expected data resource.
|
||||||
*/
|
*/
|
||||||
|
|
@ -227,7 +228,7 @@ api_data_post(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -249,7 +250,7 @@ api_data_post(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -270,7 +271,7 @@ api_data_post(clicon_handle h,
|
||||||
nullspec = (xml_spec(xdata) == NULL);
|
nullspec = (xml_spec(xdata) == NULL);
|
||||||
if (xml_apply0(xdata, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
if (xml_apply0(xdata, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (!parse_xml && nullspec){
|
if (media_in == YANG_DATA_JSON && nullspec){
|
||||||
/* json2xml decode may not have been done above in json_parse,
|
/* json2xml decode may not have been done above in json_parse,
|
||||||
need to be done here instead
|
need to be done here instead
|
||||||
UNLESS it is a root resource, then json-parse has already done it
|
UNLESS it is a root resource, then json-parse has already done it
|
||||||
|
|
@ -278,7 +279,7 @@ api_data_post(clicon_handle h,
|
||||||
if ((ret = json2xml_decode(xdata, &xerr)) < 0)
|
if ((ret = json2xml_decode(xdata, &xerr)) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (ret == 0){
|
if (ret == 0){
|
||||||
if (api_return_err(h, r, xerr, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xerr, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -318,7 +319,7 @@ api_data_post(clicon_handle h,
|
||||||
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0)
|
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if ((xe = xpath_first(xret, "//rpc-error")) != NULL){
|
if ((xe = xpath_first(xret, "//rpc-error")) != NULL){
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -340,7 +341,7 @@ api_data_post(clicon_handle h,
|
||||||
/* log errors from discard, but ignore */
|
/* log errors from discard, but ignore */
|
||||||
if ((xpath_first(xretdis, "//rpc-error")) != NULL)
|
if ((xpath_first(xretdis, "//rpc-error")) != NULL)
|
||||||
clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__);
|
clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__);
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) /* Use original xe */
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) /* Use original xe */
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -400,8 +401,8 @@ api_data_post(clicon_handle h,
|
||||||
* @param[in] yrpc Yang rpc spec
|
* @param[in] yrpc Yang rpc spec
|
||||||
* @param[in] xrpc XML pointer to rpc method
|
* @param[in] xrpc XML pointer to rpc method
|
||||||
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||||
* @param[in] use_xml Set to 0 for JSON and 1 for XML for output data
|
* @param[in] media_in Input media
|
||||||
* @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data
|
* @param[in] media_out Output media
|
||||||
* @retval 1 OK
|
* @retval 1 OK
|
||||||
* @retval 0 Fail, Error message sent
|
* @retval 0 Fail, Error message sent
|
||||||
* @retval -1 Fatal error, clicon_err called
|
* @retval -1 Fatal error, clicon_err called
|
||||||
|
|
@ -422,8 +423,8 @@ api_operations_post_input(clicon_handle h,
|
||||||
yang_stmt *yrpc,
|
yang_stmt *yrpc,
|
||||||
cxobj *xrpc,
|
cxobj *xrpc,
|
||||||
int pretty,
|
int pretty,
|
||||||
int use_xml,
|
restconf_media media_in,
|
||||||
int parse_xml)
|
restconf_media media_out)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
cxobj *xdata = NULL;
|
cxobj *xdata = NULL;
|
||||||
|
|
@ -440,7 +441,8 @@ api_operations_post_input(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
/* Parse input data as json or xml into xml */
|
/* Parse input data as json or xml into xml */
|
||||||
if (parse_xml){
|
switch (media_in){
|
||||||
|
case YANG_DATA_XML:
|
||||||
if (xml_parse_string(data, yspec, &xdata) < 0){
|
if (xml_parse_string(data, yspec, &xdata) < 0){
|
||||||
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -448,12 +450,12 @@ api_operations_post_input(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
else { /* JSON */
|
case YANG_DATA_JSON:
|
||||||
if ((ret = json_parse_str(data, yspec, &xdata, &xerr)) < 0){
|
if ((ret = json_parse_str(data, yspec, &xdata, &xerr)) < 0){
|
||||||
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -461,7 +463,7 @@ api_operations_post_input(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
@ -470,11 +472,12 @@ api_operations_post_input(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
|
} /* switch media_in */
|
||||||
xml_name_set(xdata, "data");
|
xml_name_set(xdata, "data");
|
||||||
/* Here xdata is:
|
/* Here xdata is:
|
||||||
* <data><input xmlns="urn:example:clixon">...</input></data>
|
* <data><input xmlns="urn:example:clixon">...</input></data>
|
||||||
|
|
@ -504,7 +507,7 @@ api_operations_post_input(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
@ -539,7 +542,7 @@ api_operations_post_input(clicon_handle h,
|
||||||
* @param[in] yspec Yang top-level specification
|
* @param[in] yspec Yang top-level specification
|
||||||
* @param[in] youtput Yang rpc output specification
|
* @param[in] youtput Yang rpc output specification
|
||||||
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||||
* @param[in] use_xml Set to 0 for JSON and 1 for XML for output data
|
* @param[in] media_out Output media
|
||||||
* @param[out] xoutputp Restconf JSON/XML output
|
* @param[out] xoutputp Restconf JSON/XML output
|
||||||
* @retval 1 OK
|
* @retval 1 OK
|
||||||
* @retval 0 Fail, Error message sent
|
* @retval 0 Fail, Error message sent
|
||||||
|
|
@ -554,7 +557,7 @@ api_operations_post_output(clicon_handle h,
|
||||||
yang_stmt *youtput,
|
yang_stmt *youtput,
|
||||||
char *namespace,
|
char *namespace,
|
||||||
int pretty,
|
int pretty,
|
||||||
int use_xml,
|
restconf_media media_out,
|
||||||
cxobj **xoutputp)
|
cxobj **xoutputp)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
@ -578,7 +581,7 @@ api_operations_post_output(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
@ -616,7 +619,7 @@ api_operations_post_output(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
@ -673,8 +676,8 @@ api_operations_post_output(clicon_handle h,
|
||||||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||||
* @param[in] data Stream input data
|
* @param[in] data Stream input data
|
||||||
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||||
* @param[in] use_xml Set to 0 for JSON and 1 for XML for output data
|
* @param[in] media_in Input media media
|
||||||
* @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data
|
* @param[in] media_out Output media
|
||||||
* See RFC 8040 Sec 3.6 / 4.4.2
|
* See RFC 8040 Sec 3.6 / 4.4.2
|
||||||
* @note We map post to edit-config create.
|
* @note We map post to edit-config create.
|
||||||
* POST {+restconf}/operations/<operation>
|
* POST {+restconf}/operations/<operation>
|
||||||
|
|
@ -705,8 +708,8 @@ api_operations_post(clicon_handle h,
|
||||||
cvec *qvec,
|
cvec *qvec,
|
||||||
char *data,
|
char *data,
|
||||||
int pretty,
|
int pretty,
|
||||||
int use_xml,
|
restconf_media media_in,
|
||||||
int parse_xml)
|
restconf_media media_out)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
int i;
|
int i;
|
||||||
|
|
@ -749,7 +752,7 @@ api_operations_post(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -768,7 +771,7 @@ api_operations_post(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -779,7 +782,7 @@ api_operations_post(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -808,7 +811,7 @@ api_operations_post(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -822,7 +825,7 @@ api_operations_post(clicon_handle h,
|
||||||
clicon_debug(1, "%s : 4. Parse input data: %s", __FUNCTION__, data);
|
clicon_debug(1, "%s : 4. Parse input data: %s", __FUNCTION__, data);
|
||||||
if (data && strlen(data)){
|
if (data && strlen(data)){
|
||||||
if ((ret = api_operations_post_input(h, r, data, yspec, yrpc, xbot,
|
if ((ret = api_operations_post_input(h, r, data, yspec, yrpc, xbot,
|
||||||
pretty, use_xml, parse_xml)) < 0)
|
pretty, media_in, media_out)) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
goto ok;
|
goto ok;
|
||||||
|
|
@ -849,7 +852,7 @@ api_operations_post(clicon_handle h,
|
||||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -879,7 +882,7 @@ api_operations_post(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
/* Local error: return it and quit */
|
/* Local error: return it and quit */
|
||||||
if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){
|
if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -888,7 +891,7 @@ api_operations_post(clicon_handle h,
|
||||||
if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0)
|
if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){
|
if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -907,24 +910,27 @@ api_operations_post(clicon_handle h,
|
||||||
#endif
|
#endif
|
||||||
youtput = yang_find(yrpc, Y_OUTPUT, NULL);
|
youtput = yang_find(yrpc, Y_OUTPUT, NULL);
|
||||||
if ((ret = api_operations_post_output(h, r, xret, yspec, youtput, namespace,
|
if ((ret = api_operations_post_output(h, r, xret, yspec, youtput, namespace,
|
||||||
pretty, use_xml, &xoutput)) < 0)
|
pretty, media_out, &xoutput)) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
goto ok;
|
goto ok;
|
||||||
/* xoutput should now look: <output xmlns="uri"><x>0</x></output> */
|
/* xoutput should now look: <output xmlns="uri"><x>0</x></output> */
|
||||||
FCGX_SetExitStatus(200, r->out); /* OK */
|
FCGX_SetExitStatus(200, r->out); /* OK */
|
||||||
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
|
|
||||||
|
FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out));
|
||||||
FCGX_FPrintF(r->out, "\r\n");
|
FCGX_FPrintF(r->out, "\r\n");
|
||||||
cbuf_reset(cbret);
|
cbuf_reset(cbret);
|
||||||
if (use_xml){
|
switch (media_out){
|
||||||
|
case YANG_DATA_XML:
|
||||||
if (clicon_xml2cbuf(cbret, xoutput, 0, pretty) < 0)
|
if (clicon_xml2cbuf(cbret, xoutput, 0, pretty) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
/* xoutput should now look: <output xmlns="uri"><x>0</x></output> */
|
/* xoutput should now look: <output xmlns="uri"><x>0</x></output> */
|
||||||
}
|
break;
|
||||||
else{
|
case YANG_DATA_JSON:
|
||||||
if (xml2json_cbuf(cbret, xoutput, pretty) < 0)
|
if (xml2json_cbuf(cbret, xoutput, pretty) < 0)
|
||||||
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;
|
||||||
}
|
}
|
||||||
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");
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,16 @@
|
||||||
int api_data_post(clicon_handle h, FCGX_Request *r, char *api_path,
|
int api_data_post(clicon_handle h, FCGX_Request *r, char *api_path,
|
||||||
cvec *pcvec, int pi,
|
cvec *pcvec, int pi,
|
||||||
cvec *qvec, char *data,
|
cvec *qvec, char *data,
|
||||||
int pretty, int use_xml, int parse_xml);
|
int pretty,
|
||||||
|
restconf_media media_in,
|
||||||
|
restconf_media media_out);
|
||||||
|
|
||||||
int api_operations_post(clicon_handle h, FCGX_Request *r,
|
int api_operations_post(clicon_handle h, FCGX_Request *r,
|
||||||
char *path,
|
char *path,
|
||||||
cvec *pcvec, int pi, cvec *qvec, char *data,
|
cvec *pcvec, int pi, cvec *qvec, char *data,
|
||||||
int pretty, int use_xml, int parse_xml);
|
int pretty,
|
||||||
|
restconf_media media_in,
|
||||||
|
restconf_media media_out);
|
||||||
|
|
||||||
|
|
||||||
#endif /* _RESTCONF_METHODS_POST_H_ */
|
#endif /* _RESTCONF_METHODS_POST_H_ */
|
||||||
|
|
|
||||||
|
|
@ -231,7 +231,7 @@ restconf_stream(clicon_handle h,
|
||||||
char *name,
|
char *name,
|
||||||
cvec *qvec,
|
cvec *qvec,
|
||||||
int pretty,
|
int pretty,
|
||||||
int use_xml,
|
restconf_media media_out,
|
||||||
int *sp)
|
int *sp)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
|
|
@ -269,7 +269,7 @@ restconf_stream(clicon_handle h,
|
||||||
if (clicon_rpc_netconf(h, cbuf_get(cb), &xret, &s) < 0)
|
if (clicon_rpc_netconf(h, cbuf_get(cb), &xret, &s) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){
|
if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){
|
||||||
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -355,7 +355,7 @@ api_stream(clicon_handle h,
|
||||||
char *data;
|
char *data;
|
||||||
int authenticated = 0;
|
int authenticated = 0;
|
||||||
int pretty;
|
int pretty;
|
||||||
int use_xml = 1; /* default */
|
restconf_media media_out = YANG_DATA_XML; /* XXX default */
|
||||||
cbuf *cbret = NULL;
|
cbuf *cbret = NULL;
|
||||||
cxobj *xret = NULL;
|
cxobj *xret = NULL;
|
||||||
cxobj *xerr;
|
cxobj *xerr;
|
||||||
|
|
@ -374,20 +374,20 @@ api_stream(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
/* Sanity check of path. Should be /stream/<name> */
|
/* Sanity check of path. Should be /stream/<name> */
|
||||||
if (pn != 3){
|
if (pn != 3){
|
||||||
notfound(r);
|
restconf_notfound(r);
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
if (strlen(pvec[0]) != 0){
|
if (strlen(pvec[0]) != 0){
|
||||||
retval = notfound(r);
|
retval = restconf_notfound(r);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (strcmp(pvec[1], streampath)){
|
if (strcmp(pvec[1], streampath)){
|
||||||
retval = notfound(r);
|
retval = restconf_notfound(r);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((method = pvec[2]) == NULL){
|
if ((method = pvec[2]) == NULL){
|
||||||
retval = notfound(r);
|
retval = restconf_notfound(r);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
clicon_debug(1, "%s: method=%s", __FUNCTION__, method);
|
clicon_debug(1, "%s: method=%s", __FUNCTION__, method);
|
||||||
|
|
@ -418,14 +418,14 @@ api_stream(clicon_handle h,
|
||||||
if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0)
|
if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
|
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
|
||||||
if (api_return_err(h, r, xerr, pretty, use_xml, 0) < 0)
|
if (api_return_err(h, r, xerr, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
|
clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
|
||||||
if (restconf_stream(h, r, method, qvec, pretty, use_xml, &s) < 0)
|
if (restconf_stream(h, r, method, qvec, pretty, media_out, &s) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (s != -1){
|
if (s != -1){
|
||||||
#ifdef STREAM_FORK
|
#ifdef STREAM_FORK
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# README for Clixon developers
|
# README for Clixon developers
|
||||||
|
|
||||||
* [Code documentation](#documentation)
|
* [Code documentation](#documentation)
|
||||||
* [How to work in git (branching)](#branching)
|
* [How to work in git (how-to-work-in-git)](#how-to-work-in-git)
|
||||||
* [How the meta-configure stuff works](#meta-configure)
|
* [How the meta-configure stuff works](#meta-configure)
|
||||||
* [How to debug](#debug)
|
* [How to debug](#debug)
|
||||||
* [New release](#new-release)
|
* [New release](#new-release)
|
||||||
|
|
@ -28,12 +28,15 @@ How to document the code
|
||||||
*/
|
*/
|
||||||
```
|
```
|
||||||
|
|
||||||
## Branching
|
## How to work in git
|
||||||
How to work in git (branching)
|
|
||||||
|
Clixon uses semantic versioning (https://semver.org).
|
||||||
|
|
||||||
Try to keep a single master branch always working. Currently testing is made using [Travis CI](https://travis-ci.org/clicon/clixon).
|
Try to keep a single master branch always working. Currently testing is made using [Travis CI](https://travis-ci.org/clicon/clixon).
|
||||||
|
|
||||||
However, releases are made periodically (ca every 3 months) which is more tested.
|
However, releases are made periodically (ca every 1 month) which is more tested.
|
||||||
|
|
||||||
|
A release branch can be made, eg release-4.0 where 4.0.0, 4.0.1 are tagged
|
||||||
|
|
||||||
## How the meta-configure stuff works
|
## How the meta-configure stuff works
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -321,7 +321,7 @@ ys_prune(yang_stmt *yp,
|
||||||
memmove(&yp->ys_stmt[i],
|
memmove(&yp->ys_stmt[i],
|
||||||
&yp->ys_stmt[i+1],
|
&yp->ys_stmt[i+1],
|
||||||
size);
|
size);
|
||||||
yp->ys_stmt[yp->ys_len--] = NULL;;
|
yp->ys_stmt[yp->ys_len--] = NULL;
|
||||||
done:
|
done:
|
||||||
return yc;
|
return yc;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
269
test/jukebox.sh
Executable file
269
test/jukebox.sh
Executable file
|
|
@ -0,0 +1,269 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Jukebox example from rfc 8040 Appendix A.1
|
||||||
|
# ASsumes fjukebox is set to name of yang file
|
||||||
|
|
||||||
|
cat <<EOF > $fjukebox
|
||||||
|
module example-jukebox {
|
||||||
|
|
||||||
|
namespace "http://example.com/ns/example-jukebox";
|
||||||
|
prefix "jbox";
|
||||||
|
|
||||||
|
organization "Example, Inc.";
|
||||||
|
contact "support at example.com";
|
||||||
|
description "Example Jukebox Data Model Module.";
|
||||||
|
revision "2016-08-15" {
|
||||||
|
description "Initial version.";
|
||||||
|
reference "example.com document 1-4673.";
|
||||||
|
}
|
||||||
|
|
||||||
|
identity genre {
|
||||||
|
description
|
||||||
|
"Base for all genre types.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// abbreviated list of genre classifications
|
||||||
|
identity alternative {
|
||||||
|
base genre;
|
||||||
|
description
|
||||||
|
"Alternative music.";
|
||||||
|
}
|
||||||
|
identity blues {
|
||||||
|
base genre;
|
||||||
|
description
|
||||||
|
"Blues music.";
|
||||||
|
}
|
||||||
|
identity country {
|
||||||
|
base genre;
|
||||||
|
description
|
||||||
|
"Country music.";
|
||||||
|
}
|
||||||
|
identity jazz {
|
||||||
|
base genre;
|
||||||
|
description
|
||||||
|
"Jazz music.";
|
||||||
|
}
|
||||||
|
identity pop {
|
||||||
|
base genre;
|
||||||
|
description
|
||||||
|
"Pop music.";
|
||||||
|
}
|
||||||
|
identity rock {
|
||||||
|
base genre;
|
||||||
|
description
|
||||||
|
"Rock music.";
|
||||||
|
}
|
||||||
|
|
||||||
|
container jukebox {
|
||||||
|
presence
|
||||||
|
"An empty container indicates that the jukebox
|
||||||
|
service is available.";
|
||||||
|
|
||||||
|
description
|
||||||
|
"Represents a 'jukebox' resource, with a library, playlists,
|
||||||
|
and a 'play' operation.";
|
||||||
|
|
||||||
|
container library {
|
||||||
|
|
||||||
|
description
|
||||||
|
"Represents the 'jukebox' library resource.";
|
||||||
|
|
||||||
|
list artist {
|
||||||
|
key name;
|
||||||
|
description
|
||||||
|
"Represents one 'artist' resource within the
|
||||||
|
'jukebox' library resource.";
|
||||||
|
|
||||||
|
leaf name {
|
||||||
|
type string {
|
||||||
|
length "1 .. max";
|
||||||
|
}
|
||||||
|
description
|
||||||
|
"The name of the artist.";
|
||||||
|
}
|
||||||
|
|
||||||
|
list album {
|
||||||
|
key name;
|
||||||
|
description
|
||||||
|
"Represents one 'album' resource within one
|
||||||
|
'artist' resource, within the jukebox library.";
|
||||||
|
|
||||||
|
leaf name {
|
||||||
|
type string {
|
||||||
|
length "1 .. max";
|
||||||
|
}
|
||||||
|
description
|
||||||
|
"The name of the album.";
|
||||||
|
}
|
||||||
|
|
||||||
|
leaf genre {
|
||||||
|
type identityref { base genre; }
|
||||||
|
description
|
||||||
|
"The genre identifying the type of music on
|
||||||
|
the album.";
|
||||||
|
}
|
||||||
|
|
||||||
|
leaf year {
|
||||||
|
type uint16 {
|
||||||
|
range "1900 .. max";
|
||||||
|
}
|
||||||
|
description
|
||||||
|
"The year the album was released.";
|
||||||
|
}
|
||||||
|
|
||||||
|
container admin {
|
||||||
|
description
|
||||||
|
"Administrative information for the album.";
|
||||||
|
|
||||||
|
leaf label {
|
||||||
|
type string;
|
||||||
|
description
|
||||||
|
"The label that released the album.";
|
||||||
|
}
|
||||||
|
leaf catalogue-number {
|
||||||
|
type string;
|
||||||
|
description
|
||||||
|
"The album's catalogue number.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list song {
|
||||||
|
key name;
|
||||||
|
description
|
||||||
|
"Represents one 'song' resource within one
|
||||||
|
'album' resource, within the jukebox library.";
|
||||||
|
|
||||||
|
leaf name {
|
||||||
|
type string {
|
||||||
|
length "1 .. max";
|
||||||
|
}
|
||||||
|
description
|
||||||
|
"The name of the song.";
|
||||||
|
}
|
||||||
|
|
||||||
|
leaf location {
|
||||||
|
type string;
|
||||||
|
mandatory true;
|
||||||
|
description
|
||||||
|
"The file location string of the
|
||||||
|
media file for the song.";
|
||||||
|
}
|
||||||
|
leaf format {
|
||||||
|
type string;
|
||||||
|
description
|
||||||
|
"An identifier string for the media type
|
||||||
|
for the file associated with the
|
||||||
|
'location' leaf for this entry.";
|
||||||
|
}
|
||||||
|
leaf length {
|
||||||
|
type uint32;
|
||||||
|
units "seconds";
|
||||||
|
description
|
||||||
|
"The duration of this song in seconds.";
|
||||||
|
}
|
||||||
|
} // end list 'song'
|
||||||
|
} // end list 'album'
|
||||||
|
} // end list 'artist'
|
||||||
|
|
||||||
|
leaf artist-count {
|
||||||
|
type uint32;
|
||||||
|
units "artists";
|
||||||
|
config false;
|
||||||
|
description
|
||||||
|
"Number of artists in the library.";
|
||||||
|
}
|
||||||
|
leaf album-count {
|
||||||
|
type uint32;
|
||||||
|
units "albums";
|
||||||
|
config false;
|
||||||
|
description
|
||||||
|
"Number of albums in the library.";
|
||||||
|
}
|
||||||
|
leaf song-count {
|
||||||
|
type uint32;
|
||||||
|
units "songs";
|
||||||
|
config false;
|
||||||
|
description
|
||||||
|
"Number of songs in the library.";
|
||||||
|
}
|
||||||
|
} // end library
|
||||||
|
|
||||||
|
list playlist {
|
||||||
|
key name;
|
||||||
|
description
|
||||||
|
"Example configuration data resource.";
|
||||||
|
|
||||||
|
leaf name {
|
||||||
|
type string;
|
||||||
|
description
|
||||||
|
"The name of the playlist.";
|
||||||
|
}
|
||||||
|
leaf description {
|
||||||
|
type string;
|
||||||
|
description
|
||||||
|
"A comment describing the playlist.";
|
||||||
|
}
|
||||||
|
list song {
|
||||||
|
key index;
|
||||||
|
ordered-by user;
|
||||||
|
|
||||||
|
description
|
||||||
|
"Example nested configuration data resource.";
|
||||||
|
|
||||||
|
leaf index { // not really needed
|
||||||
|
type uint32;
|
||||||
|
description
|
||||||
|
"An arbitrary integer index for this playlist song.";
|
||||||
|
}
|
||||||
|
leaf id {
|
||||||
|
type instance-identifier;
|
||||||
|
mandatory true;
|
||||||
|
description
|
||||||
|
"Song identifier. Must identify an instance of
|
||||||
|
/jukebox/library/artist/album/song/name.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
container player {
|
||||||
|
description
|
||||||
|
"Represents the jukebox player resource.";
|
||||||
|
|
||||||
|
leaf gap {
|
||||||
|
type decimal64 {
|
||||||
|
fraction-digits 1;
|
||||||
|
range "0.0 .. 2.0";
|
||||||
|
}
|
||||||
|
units "tenths of seconds";
|
||||||
|
description
|
||||||
|
"Time gap between each song.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc play {
|
||||||
|
description
|
||||||
|
"Control function for the jukebox player.";
|
||||||
|
input {
|
||||||
|
leaf playlist {
|
||||||
|
type string;
|
||||||
|
mandatory true;
|
||||||
|
description
|
||||||
|
"The playlist name.";
|
||||||
|
}
|
||||||
|
leaf song-number {
|
||||||
|
type uint32;
|
||||||
|
mandatory true;
|
||||||
|
description
|
||||||
|
"Song number in playlist to play.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
leaf-list extra{
|
||||||
|
type string;
|
||||||
|
ordered-by user;
|
||||||
|
description "Extra added to test ordered-by user inserts on leaf-lists";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -170,17 +170,17 @@ new "restconf DELETE whole datastore"
|
||||||
expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 ""
|
expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 ""
|
||||||
|
|
||||||
new "restconf set protocol tcp+udp fail"
|
new "restconf set protocol tcp+udp fail"
|
||||||
expecteq "$(curl -s -X PUT http://localhost/restconf/data/system:system/protocol -d '{"system:protocol":{"tcp": [null], "udp": [null]}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"udp"},"error-severity":"error","error-message":"Element in choice statement already exists"}}}
'
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/system:system/protocol -d '{"system:protocol":{"tcp": [null], "udp": [null]}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"udp"},"error-severity":"error","error-message":"Element in choice statement already exists"}}}
'
|
||||||
|
|
||||||
new "restconf set protocol tcp"
|
new "restconf set protocol tcp"
|
||||||
expecteq "$(curl -s -X PUT http://localhost/restconf/data/system:system/protocol -d {\"system:protocol\":{\"tcp\":[null]}})" 0 ""
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/system:system/protocol -d {\"system:protocol\":{\"tcp\":[null]}})" 0 ""
|
||||||
|
|
||||||
new "restconf get protocol tcp"
|
new "restconf get protocol tcp"
|
||||||
expecteq "$(curl -s -X GET http://localhost/restconf/data/system:system)" 0 '{"system:system":{"protocol":{"tcp":[null]}}}
|
expecteq "$(curl -s -X GET http://localhost/restconf/data/system:system)" 0 '{"system:system":{"protocol":{"tcp":[null]}}}
|
||||||
'
|
'
|
||||||
|
|
||||||
new "restconf set protocol tcp+udp fail"
|
new "restconf set protocol tcp+udp fail"
|
||||||
expecteq "$(curl -s -X PUT http://localhost/restconf/data/system:system/protocol -d '{"system:protocol":{"tcp": [null], "udp": [null]}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"udp"},"error-severity":"error","error-message":"Element in choice statement already exists"}}}
'
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/system:system/protocol -d '{"system:protocol":{"tcp": [null], "udp": [null]}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"udp"},"error-severity":"error","error-message":"Element in choice statement already exists"}}}
'
|
||||||
|
|
||||||
new "cli set protocol udp"
|
new "cli set protocol udp"
|
||||||
expectfn "$clixon_cli -1 -f $cfg -l o set system protocol udp" 0 "^$"
|
expectfn "$clixon_cli -1 -f $cfg -l o set system protocol udp" 0 "^$"
|
||||||
|
|
|
||||||
|
|
@ -267,7 +267,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<
|
||||||
# 2. set identity in other module with restconf , read it with restconf and netconf
|
# 2. set identity in other module with restconf , read it with restconf and netconf
|
||||||
# 3. set identity in other module with netconf, read it with restconf and netconf
|
# 3. set identity in other module with netconf, read it with restconf and netconf
|
||||||
new "restconf add own identity"
|
new "restconf add own identity"
|
||||||
expectpart "$(curl -s -i -X PUT http://localhost/restconf/data/example:crypto -d '{"example:crypto":"example:aes"}')" 0 'HTTP/1.1 201 Created'
|
expectpart "$(curl -s -i -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:crypto -d '{"example:crypto":"example:aes"}')" 0 'HTTP/1.1 201 Created'
|
||||||
|
|
||||||
new "restconf get own identity"
|
new "restconf get own identity"
|
||||||
expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example:crypto)" 0 'HTTP/1.1 200 OK' '{"example:crypto":"aes"}'
|
expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example:crypto)" 0 'HTTP/1.1 200 OK' '{"example:crypto":"aes"}'
|
||||||
|
|
@ -280,10 +280,10 @@ expectpart "$(curl -s -i -X DELETE http://localhost/restconf/data/example:crypt
|
||||||
|
|
||||||
# 2. set identity in other module with restconf , read it with restconf and netconf
|
# 2. set identity in other module with restconf , read it with restconf and netconf
|
||||||
new "restconf add POST instead of PUT (should fail)"
|
new "restconf add POST instead of PUT (should fail)"
|
||||||
expectpart "$(curl -s -i -X POST http://localhost/restconf/data/example:crypto -d '{"example:crypto":"example-des:des3"}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Leaf contains sub-element"}}}'
|
expectpart "$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:crypto -d '{"example:crypto":"example-des:des3"}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Leaf contains sub-element"}}}'
|
||||||
|
|
||||||
new "restconf add other (des) identity using POST"
|
new "restconf add other (des) identity using POST"
|
||||||
expectpart "$(curl -s -i -X POST http://localhost/restconf/data -d '{"example:crypto":"example-des:des3"}')" 0 'HTTP/1.1 201 Created' 'Location: http://localhost/restconf/data/example:crypto'
|
expectpart "$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data -d '{"example:crypto":"example-des:des3"}')" 0 'HTTP/1.1 201 Created' 'Location: http://localhost/restconf/data/example:crypto'
|
||||||
|
|
||||||
new "restconf get other identity"
|
new "restconf get other identity"
|
||||||
expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example:crypto)" 0 'HTTP/1.1 200 OK' '{"example:crypto":"example-des:des3"}'
|
expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example:crypto)" 0 'HTTP/1.1 200 OK' '{"example:crypto":"example-des:des3"}'
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-exam
|
||||||
|
|
||||||
# explicitly disable nacm (regression on netgate bug)
|
# explicitly disable nacm (regression on netgate bug)
|
||||||
new "disable nacm"
|
new "disable nacm"
|
||||||
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": false}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 ""
|
expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": false}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 ""
|
||||||
|
|
||||||
new "auth set authentication config"
|
new "auth set authentication config"
|
||||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config>$RULES</config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config>$RULES</config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
@ -157,7 +157,7 @@ expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-exam
|
||||||
#----------------Enable NACM
|
#----------------Enable NACM
|
||||||
|
|
||||||
new "enable nacm"
|
new "enable nacm"
|
||||||
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 ""
|
expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 ""
|
||||||
|
|
||||||
new "admin get nacm"
|
new "admin get nacm"
|
||||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0}
|
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0}
|
||||||
|
|
@ -171,13 +171,13 @@ new "guest get nacm"
|
||||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
||||||
|
|
||||||
new "admin edit nacm"
|
new "admin edit nacm"
|
||||||
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"nacm-example:x":1}' http://localhost/restconf/data/nacm-example:x)" 0 ""
|
expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x":1}' http://localhost/restconf/data/nacm-example:x)" 0 ""
|
||||||
|
|
||||||
new "limited edit nacm"
|
new "limited edit nacm"
|
||||||
expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"nacm-example:x": 2}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
expecteq "$(curl -u wilma:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 2}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||||
|
|
||||||
new "guest edit nacm"
|
new "guest edit nacm"
|
||||||
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"nacm-example:x": 3}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
expecteq "$(curl -u guest:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 3}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
||||||
|
|
||||||
new "Kill restconf daemon"
|
new "Kill restconf daemon"
|
||||||
stop_restconf
|
stop_restconf
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ EOF
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
new "edit new 99"
|
new "edit new 99"
|
||||||
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"nacm-example:x": 99}' http://localhost/restconf/data/nacm-example:x)" 0 "$ret"
|
expecteq "$(curl -u guest:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 99}' http://localhost/restconf/data/nacm-example:x)" 0 "$ret"
|
||||||
|
|
||||||
#----------- Then second get
|
#----------- Then second get
|
||||||
case "$ret3" in
|
case "$ret3" in
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,7 @@ expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data)" 0 '{"da
|
||||||
'
|
'
|
||||||
|
|
||||||
new "Set x to 0"
|
new "Set x to 0"
|
||||||
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"nacm-example:x": 0}' http://localhost/restconf/data/nacm-example:x)" 0 ""
|
expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 0}' http://localhost/restconf/data/nacm-example:x)" 0 ""
|
||||||
|
|
||||||
new "auth get (no user: access denied)"
|
new "auth get (no user: access denied)"
|
||||||
expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}}
'
|
expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}}
'
|
||||||
|
|
@ -181,13 +181,13 @@ new "guest get nacm"
|
||||||
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
||||||
|
|
||||||
new "admin edit nacm"
|
new "admin edit nacm"
|
||||||
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"nacm-example:x": 1}' http://localhost/restconf/data/nacm-example:x)" 0 ""
|
expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 1}' http://localhost/restconf/data/nacm-example:x)" 0 ""
|
||||||
|
|
||||||
new "limited edit nacm"
|
new "limited edit nacm"
|
||||||
expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"nacm-example:x": 2}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
expecteq "$(curl -u wilma:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 2}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||||
|
|
||||||
new "guest edit nacm"
|
new "guest edit nacm"
|
||||||
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"nacm-example:x": 3}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
expecteq "$(curl -u guest:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 3}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
||||||
|
|
||||||
new "cli show conf as admin"
|
new "cli show conf as admin"
|
||||||
expectfn "$clixon_cli -1 -U andy -l o -f $cfg show conf" 0 "^x 1;$"
|
expectfn "$clixon_cli -1 -U andy -l o -f $cfg show conf" 0 "^x 1;$"
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ new "commit it"
|
||||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "enable nacm"
|
new "enable nacm"
|
||||||
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 ""
|
expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 ""
|
||||||
|
|
||||||
#--------------- nacm enabled
|
#--------------- nacm enabled
|
||||||
|
|
||||||
|
|
@ -233,21 +233,21 @@ expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data)" 0 '{"i
|
||||||
#------- RPC operation
|
#------- RPC operation
|
||||||
|
|
||||||
new "admin rpc ok"
|
new "admin rpc ok"
|
||||||
expecteq "$(curl -u andy:bar -s -X POST -d '{"clixon-example:input":{"x":"78"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"78","y":"42"}}
|
expecteq "$(curl -u andy:bar -s -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"78"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"78","y":"42"}}
|
||||||
'
|
'
|
||||||
|
|
||||||
new "admin rpc netconf ok"
|
new "admin rpc netconf ok"
|
||||||
expecteof "$clixon_netconf -U andy -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><example xmlns="urn:example:clixon"><x>0</x></example></rpc>]]>]]>' 0 '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><x xmlns="urn:example:clixon">0</x><y xmlns="urn:example:clixon">42</y></rpc-reply>]]>]]>$'
|
expecteof "$clixon_netconf -U andy -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><example xmlns="urn:example:clixon"><x>0</x></example></rpc>]]>]]>' 0 '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><x xmlns="urn:example:clixon">0</x><y xmlns="urn:example:clixon">42</y></rpc-reply>]]>]]>$'
|
||||||
|
|
||||||
new "limit rpc ok"
|
new "limit rpc ok"
|
||||||
expecteq "$(curl -u wilma:bar -s -X POST http://localhost/restconf/operations/clixon-example:example -d '{"clixon-example:input":{"x":42}}' )" 0 '{"clixon-example:output":{"x":"42","y":"42"}}
|
expecteq "$(curl -u wilma:bar -s -X POST http://localhost/restconf/operations/clixon-example:example -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' )" 0 '{"clixon-example:output":{"x":"42","y":"42"}}
|
||||||
'
|
'
|
||||||
|
|
||||||
new "limit rpc netconf ok"
|
new "limit rpc netconf ok"
|
||||||
expecteof "$clixon_netconf -U wilma -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><example xmlns="urn:example:clixon"><x>0</x></example></rpc>]]>]]>' 0 '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><x xmlns="urn:example:clixon">0</x><y xmlns="urn:example:clixon">42</y></rpc-reply>]]>]]>$'
|
expecteof "$clixon_netconf -U wilma -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><example xmlns="urn:example:clixon"><x>0</x></example></rpc>]]>]]>' 0 '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><x xmlns="urn:example:clixon">0</x><y xmlns="urn:example:clixon">42</y></rpc-reply>]]>]]>$'
|
||||||
|
|
||||||
new "guest rpc fail"
|
new "guest rpc fail"
|
||||||
expecteq "$(curl -u guest:bar -s -X POST http://localhost/restconf/operations/clixon-example:example -d '{"clixon-example:input":{"x":42}}' )" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
expecteq "$(curl -u guest:bar -s -X POST http://localhost/restconf/operations/clixon-example:example -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' )" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}
'
|
||||||
|
|
||||||
new "guest rpc netconf fail"
|
new "guest rpc netconf fail"
|
||||||
expecteof "$clixon_netconf -U guest -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><example xmlns="urn:example:clixon"><x>0</x></example></rpc>]]>]]>' 0 '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><rpc-error><error-type>application</error-type><error-tag>access-denied</error-tag><error-severity>error</error-severity><error-message>access denied</error-message></rpc-error></rpc-reply>]]>]]>$'
|
expecteof "$clixon_netconf -U guest -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><example xmlns="urn:example:clixon"><x>0</x></example></rpc>]]>]]>' 0 '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><rpc-error><error-type>application</error-type><error-tag>access-denied</error-tag><error-severity>error</error-severity><error-message>access denied</error-message></rpc-error></rpc-reply>]]>]]>$'
|
||||||
|
|
@ -255,7 +255,7 @@ expecteof "$clixon_netconf -U guest -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml
|
||||||
#------------------ Set read-default permit
|
#------------------ Set read-default permit
|
||||||
|
|
||||||
new "admin set read-default permit"
|
new "admin set read-default permit"
|
||||||
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:read-default":"permit"}' http://localhost/restconf/data/ietf-netconf-acm:nacm/read-default)" 0 ""
|
expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:read-default":"permit"}' http://localhost/restconf/data/ietf-netconf-acm:nacm/read-default)" 0 ""
|
||||||
|
|
||||||
new "limit read ok"
|
new "limit read ok"
|
||||||
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate)" 0 '{"clixon-example:translate":[{"k":"key42","value":"val42"},{"k":"key43","value":"val43"}]}
|
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate)" 0 '{"clixon-example:translate":[{"k":"key42","value":"val42"},{"k":"key43","value":"val43"}]}
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,8 @@ nacm(){
|
||||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "enable nacm"
|
new "enable nacm"
|
||||||
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 ""
|
expectpart "$(curl -u andy:bar -sS -X PUT -H 'Content-Type: application/yang-data+json' -d '{"ietf-netconf-acm:enable-nacm":true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 ""
|
||||||
|
# -H 'Content-Type: application/yang-data+json'
|
||||||
}
|
}
|
||||||
|
|
||||||
#--------------- enable nacm
|
#--------------- enable nacm
|
||||||
|
|
@ -179,7 +180,7 @@ nacm
|
||||||
# replace all, then must include NACM rules as well
|
# replace all, then must include NACM rules as well
|
||||||
MSG="<data>$RULES</data>"
|
MSG="<data>$RULES</data>"
|
||||||
new "update root list permit"
|
new "update root list permit"
|
||||||
expecteq "$(curl -u andy:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data -d "$MSG")" 0 ''
|
expectpart "$(curl -u andy:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data -d "$MSG")" 0 ''
|
||||||
|
|
||||||
new "delete root list deny"
|
new "delete root list deny"
|
||||||
expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||||
|
|
@ -238,13 +239,13 @@ expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data/nacm-
|
||||||
|
|
||||||
#----- default deny (clixon-example limit and guest have default access)
|
#----- default deny (clixon-example limit and guest have default access)
|
||||||
new "default create list deny"
|
new "default create list deny"
|
||||||
expecteq "$(curl -u wilma:bar -sS -X PUT http://localhost/restconf/data/clixon-example:translate=key42 -d '{"clixon-example:translate": [{"k":"key42","value":"val42"}]}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
expecteq "$(curl -u wilma:bar -sS -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/clixon-example:translate=key42 -d '{"clixon-example:translate": [{"k":"key42","value":"val42"}]}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||||
|
|
||||||
new "create list permit"
|
new "create list permit"
|
||||||
expecteq "$(curl -u andy:bar -sS -X PUT http://localhost/restconf/data/clixon-example:translate=key42 -d '{"clixon-example:translate": [{"k":"key42","value":"val42"}]}')" 0 ''
|
expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/clixon-example:translate=key42 -d '{"clixon-example:translate": [{"k":"key42","value":"val42"}]}')" 0 ''
|
||||||
|
|
||||||
new "default update list deny"
|
new "default update list deny"
|
||||||
expecteq "$(curl -u wilma:bar -sS -X PUT http://localhost/restconf/data/clixon-example:translate=key42 -d '{"clixon-example:translate": [{"k":"key42","value":"val99"}]}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
expecteq "$(curl -u wilma:bar -sS -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/clixon-example:translate=key42 -d '{"clixon-example:translate": [{"k":"key42","value":"val99"}]}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||||
|
|
||||||
new "default delete list deny"
|
new "default delete list deny"
|
||||||
expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data/clixon-example:translate=key42)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data/clixon-example:translate=key42)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@ new "commit it"
|
||||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "enable nacm"
|
new "enable nacm"
|
||||||
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 ""
|
expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 ""
|
||||||
|
|
||||||
#--------------- nacm enabled
|
#--------------- nacm enabled
|
||||||
|
|
||||||
|
|
@ -205,14 +205,14 @@ new "commit it"
|
||||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "enable nacm"
|
new "enable nacm"
|
||||||
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 ""
|
expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 ""
|
||||||
|
|
||||||
# Rule 3: permit-edit-config
|
# Rule 3: permit-edit-config
|
||||||
new "permit-edit-config: limited ok restconf"
|
new "permit-edit-config: limited ok restconf"
|
||||||
expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"nacm-example:x":2}' http://localhost/restconf/data/nacm-example:x)" 0 ''
|
expecteq "$(curl -u wilma:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x":2}' http://localhost/restconf/data/nacm-example:x)" 0 ''
|
||||||
|
|
||||||
new "permit-edit-config: guest fail restconf"
|
new "permit-edit-config: guest fail restconf"
|
||||||
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"nacm-example:x":2}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
expecteq "$(curl -u guest:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x":2}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}
'
|
||||||
|
|
||||||
new "Kill restconf daemon"
|
new "Kill restconf daemon"
|
||||||
stop_restconf
|
stop_restconf
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ expecteq "$(curl -s -X GET http://localhost/.well-known/host-meta)" 0 "<XRD xmln
|
||||||
</XRD>
"
|
</XRD>
"
|
||||||
|
|
||||||
new "restconf get restconf resource. RFC 8040 3.3 (json)"
|
new "restconf get restconf resource. RFC 8040 3.3 (json)"
|
||||||
expecteq "$(curl -sG http://localhost/restconf)" 0 '{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2016-06-21"}}
|
expecteq "$(curl -sG -H "Accept: application/yang-data+json" http://localhost/restconf)" 0 '{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2016-06-21"}}
|
||||||
'
|
'
|
||||||
|
|
||||||
new "restconf get restconf resource. RFC 8040 3.3 (xml)"
|
new "restconf get restconf resource. RFC 8040 3.3 (xml)"
|
||||||
|
|
@ -108,10 +108,16 @@ expectfn "curl -s -I http://localhost/restconf/data" 0 "HTTP/1.1 200 OK"
|
||||||
#Content-Type: application/yang-data+json"
|
#Content-Type: application/yang-data+json"
|
||||||
|
|
||||||
new "restconf empty rpc"
|
new "restconf empty rpc"
|
||||||
expecteq "$(curl -s -X POST -d {\"clixon-example:input\":null} http://localhost/restconf/operations/clixon-example:empty)" 0 ""
|
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":null} http://localhost/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content"
|
||||||
|
|
||||||
|
new "restconf empty rpc, default media type should fail"
|
||||||
|
expectpart "$(curl -si -X POST -d {\"clixon-example:input\":null} http://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type'
|
||||||
|
|
||||||
|
new "restconf empty rpc, default media type should fail (JSON)"
|
||||||
|
expectpart "$(curl -si -X POST -H "Accept: application/yang-data+json" -d {\"clixon-example:input\":null} http://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type'
|
||||||
|
|
||||||
new "restconf empty rpc with extra args (should fail)"
|
new "restconf empty rpc with extra args (should fail)"
|
||||||
expecteq "$(curl -s -X POST -d {\"clixon-example:input\":{\"extra\":null}} http://localhost/restconf/operations/clixon-example:empty)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error"}}}
'
|
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":{\"extra\":null}} http://localhost/restconf/operations/clixon-example:empty)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error"}}}
'
|
||||||
|
|
||||||
new "restconf get empty config + state json"
|
new "restconf get empty config + state json"
|
||||||
expecteq "$(curl -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["42","41","43"]}}
|
expecteq "$(curl -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["42","41","43"]}}
|
||||||
|
|
@ -160,10 +166,10 @@ expecteq "$(curl -s -X GET http://localhost/restconf/data/clixon-example:state)"
|
||||||
|
|
||||||
# Exact match
|
# Exact match
|
||||||
new "restconf Add subtree eth/0/0 to datastore using POST"
|
new "restconf Add subtree eth/0/0 to datastore using POST"
|
||||||
expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}} http://localhost/restconf/data' 0 'HTTP/1.1 201 Created'
|
expectpart "$(curl -s -i -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' http://localhost/restconf/data)" 0 'HTTP/1.1 201 Created' 'Location: http://localhost/restconf/data/ietf-interfaces:interfaces'
|
||||||
|
|
||||||
new "restconf Re-add subtree eth/0/0 which should give error"
|
new "restconf Re-add subtree eth/0/0 which should give error"
|
||||||
expectfn 'curl -s -X POST -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}'
|
expectpart "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}'
|
||||||
|
|
||||||
# XXX Cant get this to work
|
# XXX Cant get this to work
|
||||||
#expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"clixon-example:eth\",\"enabled\":true}}} http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}'
|
#expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"clixon-example:eth\",\"enabled\":true}}} http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}'
|
||||||
|
|
@ -180,22 +186,21 @@ expectfn "curl -sG http://localhost/restconf/data/clixon-example:state" 0 "$stat
|
||||||
"
|
"
|
||||||
|
|
||||||
new "restconf Add interfaces subtree eth/0/0 using POST"
|
new "restconf Add interfaces subtree eth/0/0 using POST"
|
||||||
expectpart "$(curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}')" 0 ""
|
expectpart "$(curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}')" 0 ""
|
||||||
#expectfn 'curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces -d {"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' 0 ""
|
|
||||||
|
|
||||||
new "restconf Check eth/0/0 added config"
|
new "restconf Check eth/0/0 added config"
|
||||||
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up"}]}}
|
expectpart "$(curl -s -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up"}\]}}
|
||||||
'
|
'
|
||||||
|
|
||||||
new "restconf Check eth/0/0 added state"
|
new "restconf Check eth/0/0 added state"
|
||||||
expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["42","41","43"]}}
|
expectpart "$(curl -s -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":\["42","41","43"\]}}
|
||||||
'
|
'
|
||||||
|
|
||||||
new "restconf Re-post eth/0/0 which should generate error"
|
new "restconf Re-post eth/0/0 which should generate error"
|
||||||
expecteq "$(curl -s -X POST -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}
'
|
expectpart "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}
'
|
||||||
|
|
||||||
new "Add leaf description using POST"
|
new "Add leaf description using POST"
|
||||||
expecteq "$(curl -s -X POST -d '{"ietf-interfaces:description":"The-first-interface"}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 ""
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:description":"The-first-interface"}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 ""
|
||||||
|
|
||||||
new "Add nothing using POST"
|
new "Add nothing using POST"
|
||||||
expectfn 'curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":" on line 1: syntax error at or before:'
|
expectfn 'curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":" on line 1: syntax error at or before:'
|
||||||
|
|
@ -214,33 +219,33 @@ new "restconf Re-Delete eth/0/0 using none should generate error"
|
||||||
expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-missing","error-severity":"error","error-message":"Data does not exist; cannot delete resource"}}}
'
|
expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-missing","error-severity":"error","error-message":"Data does not exist; cannot delete resource"}}}
'
|
||||||
|
|
||||||
new "restconf Add subtree eth/0/0 using PUT"
|
new "restconf Add subtree eth/0/0 using PUT"
|
||||||
expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 ""
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 ""
|
||||||
|
|
||||||
new "restconf get subtree"
|
new "restconf get subtree"
|
||||||
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up"}]}}
|
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up"}]}}
|
||||||
'
|
'
|
||||||
|
|
||||||
new "restconf rpc using POST json"
|
new "restconf rpc using POST json"
|
||||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"x":42}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"42","y":"42"}}
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"42","y":"42"}}
|
||||||
'
|
'
|
||||||
|
|
||||||
new "restconf rpc using POST json wrong"
|
new "restconf rpc using POST json wrong"
|
||||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error"}}}
'
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error"}}}
'
|
||||||
|
|
||||||
new "restconf rpc non-existing rpc without namespace"
|
new "restconf rpc non-existing rpc without namespace"
|
||||||
expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}}
'
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://localhost/restconf/operations/kalle)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}}
'
|
||||||
|
|
||||||
new "restconf rpc non-existing rpc"
|
new "restconf rpc non-existing rpc"
|
||||||
expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/clixon-example:kalle)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}}
'
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://localhost/restconf/operations/clixon-example:kalle)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}}
'
|
||||||
|
|
||||||
new "restconf rpc missing name"
|
new "restconf rpc missing name"
|
||||||
expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"Operation name expected"}}}
'
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://localhost/restconf/operations)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"Operation name expected"}}}
'
|
||||||
|
|
||||||
new "restconf rpc missing input"
|
new "restconf rpc missing input"
|
||||||
expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"restconf RPC does not have input statement"}}}
'
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"restconf RPC does not have input statement"}}}
'
|
||||||
|
|
||||||
new "restconf rpc using POST xml"
|
new "restconf rpc using POST xml"
|
||||||
ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":42}}' http://localhost/restconf/operations/clixon-example:example)
|
ret=$(curl -s -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":42}}' http://localhost/restconf/operations/clixon-example:example)
|
||||||
expect='<output xmlns="urn:example:clixon"><x>42</x><y>42</y></output>'
|
expect='<output xmlns="urn:example:clixon"><x>42</x><y>42</y></output>'
|
||||||
match=`echo $ret | grep -EZo "$expect"`
|
match=`echo $ret | grep -EZo "$expect"`
|
||||||
if [ -z "$match" ]; then
|
if [ -z "$match" ]; then
|
||||||
|
|
@ -248,10 +253,10 @@ if [ -z "$match" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "restconf rpc using wrong prefix"
|
new "restconf rpc using wrong prefix"
|
||||||
expecteq "$(curl -s -X POST -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:example)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"yang module not found"}}}
'
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:example)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"yang module not found"}}}
'
|
||||||
|
|
||||||
new "restconf local client rpc using POST xml"
|
new "restconf local client rpc using POST xml"
|
||||||
ret=$(curl -s -i -X POST -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":"example"}}' http://localhost/restconf/operations/clixon-example:client-rpc)
|
ret=$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":"example"}}' http://localhost/restconf/operations/clixon-example:client-rpc)
|
||||||
expect='<output xmlns="urn:example:clixon"><x>example</x></output>'
|
expect='<output xmlns="urn:example:clixon"><x>example</x></output>'
|
||||||
match=`echo $ret | grep -EZo "$expect"`
|
match=`echo $ret | grep -EZo "$expect"`
|
||||||
if [ -z "$match" ]; then
|
if [ -z "$match" ]; then
|
||||||
|
|
@ -259,10 +264,10 @@ if [ -z "$match" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "restconf Add subtree without key (expected error)"
|
new "restconf Add subtree without key (expected error)"
|
||||||
expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key, expected '"'"'=restval'"'"'"}}}
'
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key, expected '"'"'=restval'"'"'"}}}
'
|
||||||
|
|
||||||
new "restconf Add subtree with too many keys (expected error)"
|
new "restconf Add subtree with too many keys (expected error)"
|
||||||
expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}}
'
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}}
'
|
||||||
|
|
||||||
new "Kill restconf daemon"
|
new "Kill restconf daemon"
|
||||||
stop_restconf
|
stop_restconf
|
||||||
|
|
|
||||||
|
|
@ -90,13 +90,13 @@ wait_backend
|
||||||
wait_restconf
|
wait_restconf
|
||||||
|
|
||||||
new "restconf POST tree without key"
|
new "restconf POST tree without key"
|
||||||
expectfn 'curl -s -X POST -d {"example:cont1":{"interface":{"type":"regular"}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"name"},"error-severity":"error","error-message":"Mandatory key"}}}
'
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"type":"regular"}}}' http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"name"},"error-severity":"error","error-message":"Mandatory key"}}}
'
|
||||||
|
|
||||||
new "restconf POST initial tree"
|
new "restconf POST initial tree"
|
||||||
expectfn 'curl -s -X POST -d {"example:cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 ""
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}' http://localhost/restconf/data)" 0 ""
|
||||||
|
|
||||||
new "restconf POST top without namespace"
|
new "restconf POST top without namespace"
|
||||||
expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"cont1"},"error-severity":"error","error-message":"Unassigned yang spec"}}}'
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"cont1":{"interface":{"name":"local0","type":"regular"}}}' http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"cont1"},"error-severity":"error","error-message":"Unassigned yang spec"}}}
'
|
||||||
|
|
||||||
new "restconf GET datastore initial"
|
new "restconf GET datastore initial"
|
||||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}'
|
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}'
|
||||||
|
|
@ -116,22 +116,22 @@ new "restconf GET if-type"
|
||||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1/interface=local0/type" 0 '{"example:type":"regular"}'
|
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1/interface=local0/type" 0 '{"example:type":"regular"}'
|
||||||
|
|
||||||
new "restconf POST interface without mandatory type"
|
new "restconf POST interface without mandatory type"
|
||||||
expectfn 'curl -s -X POST http://localhost/restconf/data/example:cont1 -d {"example:interface":{"name":"TEST"}}' 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"type"},"error-severity":"error","error-message":"Mandatory variable"}}}
'
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:cont1 -d '{"example:interface":{"name":"TEST"}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"type"},"error-severity":"error","error-message":"Mandatory variable"}}}
'
|
||||||
|
|
||||||
new "restconf POST interface without mandatory key"
|
new "restconf POST interface without mandatory key"
|
||||||
expectfn 'curl -s -X POST http://localhost/restconf/data/example:cont1 -d {"example:interface":{"type":"regular"}}' 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"name"},"error-severity":"error","error-message":"Mandatory key"}}}
'
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:cont1 -d '{"example:interface":{"type":"regular"}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"name"},"error-severity":"error","error-message":"Mandatory key"}}}
'
|
||||||
|
|
||||||
new "restconf POST interface"
|
new "restconf POST interface"
|
||||||
expectfn 'curl -s -X POST -d {"example:interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/example:cont1' 0 ""
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/example:cont1)" 0 ""
|
||||||
|
|
||||||
new "restconf POST interface without namespace"
|
new "restconf POST interface without namespace"
|
||||||
expectfn 'curl -s -X POST -d {"interface":{"name":"TEST2","type":"eth0"}} http://localhost/restconf/data/example:cont1' 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Data is not prefixed with matching namespace"}}}'
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"interface":{"name":"TEST2","type":"eth0"}}' http://localhost/restconf/data/example:cont1)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Data is not prefixed with matching namespace"}}}
'
|
||||||
|
|
||||||
new "restconf POST again"
|
new "restconf POST again"
|
||||||
expecteq "$(curl -s -X POST -d '{"example:interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/example:cont1)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}
'
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/example:cont1)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}
'
|
||||||
|
|
||||||
new "restconf POST from top"
|
new "restconf POST from top"
|
||||||
expecteq "$(curl -s -X POST -d '{"example:cont1":{"interface":{"name":"TEST","type":"eth0"}}}' http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}
'
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"TEST","type":"eth0"}}}' http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}
'
|
||||||
|
|
||||||
new "restconf DELETE"
|
new "restconf DELETE"
|
||||||
expectfn 'curl -s -X DELETE http://localhost/restconf/data/example:cont1' 0 ""
|
expectfn 'curl -s -X DELETE http://localhost/restconf/data/example:cont1' 0 ""
|
||||||
|
|
@ -140,7 +140,7 @@ new "restconf GET null datastore"
|
||||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors":{"error":{"rpc-error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}}'
|
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors":{"error":{"rpc-error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}}'
|
||||||
|
|
||||||
new "restconf POST initial tree"
|
new "restconf POST initial tree"
|
||||||
expectfn 'curl -s -X POST -d {"example:cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 ""
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}' http://localhost/restconf/data)" 0 ""
|
||||||
|
|
||||||
new "restconf GET initial tree"
|
new "restconf GET initial tree"
|
||||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}'
|
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}'
|
||||||
|
|
@ -152,41 +152,41 @@ new "restconf GET null datastore"
|
||||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors":{"error":{"rpc-error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}}'
|
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors":{"error":{"rpc-error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}}'
|
||||||
|
|
||||||
new "restconf PUT initial datastore"
|
new "restconf PUT initial datastore"
|
||||||
expectfn 'curl -s -X PUT -d {"data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 ""
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' http://localhost/restconf/data)" 0 ""
|
||||||
|
|
||||||
new "restconf GET datastore"
|
new "restconf GET datastore"
|
||||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}'
|
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}'
|
||||||
|
|
||||||
new "restconf PUT replace datastore"
|
new "restconf PUT replace datastore"
|
||||||
expectfn 'curl -s -X PUT -d {"data":{"example:cont2":{"name":"foo"}}} http://localhost/restconf/data' 0 ""
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"data":{"example:cont2":{"name":"foo"}}}' http://localhost/restconf/data)" 0 ""
|
||||||
|
|
||||||
new "restconf GET replaced datastore"
|
new "restconf GET replaced datastore"
|
||||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont2" 0 '{"example:cont2":{"name":"foo"}}'
|
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont2" 0 '{"example:cont2":{"name":"foo"}}'
|
||||||
|
|
||||||
new "restconf PUT initial datastore again"
|
new "restconf PUT initial datastore again"
|
||||||
expectfn 'curl -s -X PUT -d {"data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 ""
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' http://localhost/restconf/data)" 0 ""
|
||||||
|
|
||||||
new "restconf PUT change interface"
|
new "restconf PUT change interface"
|
||||||
expectfn 'curl -s -X PUT -d {"example:interface":{"name":"local0","type":"atm0"}} http://localhost/restconf/data/example:cont1/interface=local0' 0 ""
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"local0","type":"atm0"}}' http://localhost/restconf/data/example:cont1/interface=local0)" 0 ""
|
||||||
|
|
||||||
new "restconf GET datastore atm"
|
new "restconf GET datastore atm"
|
||||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"atm0"}\]}}'
|
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"atm0"}\]}}'
|
||||||
|
|
||||||
new "restconf PUT add interface"
|
new "restconf PUT add interface"
|
||||||
expectfn 'curl -s -X PUT -d {"example:interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/example:cont1/interface=TEST' 0 ""
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/example:cont1/interface=TEST)" 0 ""
|
||||||
|
|
||||||
new "restconf PUT change key error"
|
new "restconf PUT change key error"
|
||||||
expectfn 'curl -is -X PUT -d {"example:interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/example:cont1/interface=TEST' 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}'
|
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"ALPHA","type":"eth0"}}' http://localhost/restconf/data/example:cont1/interface=TEST)" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}
'
|
||||||
|
|
||||||
new "restconf PUT change type to eth0 (non-key sub-element to list)"
|
new "restconf PUT change type to eth0 (non-key sub-element to list)"
|
||||||
expectfn 'curl -s -X PUT -d {"example:type":"eth0"} http://localhost/restconf/data/example:cont1/interface=local0/type' 0 ""
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:type":"eth0"}' http://localhost/restconf/data/example:cont1/interface=local0/type)" 0 ""
|
||||||
|
|
||||||
new "restconf GET datastore eth"
|
new "restconf GET datastore eth"
|
||||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1/interface=local0" 0 '{"example:interface":\[{"name":"local0","type":"eth0"}\]}'
|
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1/interface=local0" 0 '{"example:interface":\[{"name":"local0","type":"eth0"}\]}'
|
||||||
|
|
||||||
#--------------- json type tests
|
#--------------- json type tests
|
||||||
new "restconf POST type x3"
|
new "restconf POST type x3"
|
||||||
expectfn 'curl -s -X POST -d {"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}} http://localhost/restconf/data' 0 ''
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}}' http://localhost/restconf/data)" 0 ''
|
||||||
|
|
||||||
new "restconf POST type x3"
|
new "restconf POST type x3"
|
||||||
expectfn 'curl -s -X GET http://localhost/restconf/data/example:types' 0 '{"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}}'
|
expectfn 'curl -s -X GET http://localhost/restconf/data/example:types' 0 '{"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}}'
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,12 @@
|
||||||
# instance that does not exist, then an error response containing a "404 Not
|
# 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
|
# Found" status-line MUST be returned by the server. The error-tag
|
||||||
# value "invalid-value" is used in this case.
|
# value "invalid-value" is used in this case.
|
||||||
|
# RFC 7231:
|
||||||
|
# Response messages with an error status code
|
||||||
|
# usually contain a payload that represents the error condition, such
|
||||||
|
# that it describes the error state and what next steps are suggested
|
||||||
|
# for resolving it.
|
||||||
|
|
||||||
|
|
||||||
# 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,8 +7,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
|
||||||
fyang=$dir/example-jukebox.yang
|
fjukebox=$dir/example-jukebox.yang
|
||||||
fxml=$dir/initial.xml
|
|
||||||
|
|
||||||
# <CLICON_YANG_MODULE_MAIN>example</CLICON_YANG_MODULE_MAIN>
|
# <CLICON_YANG_MODULE_MAIN>example</CLICON_YANG_MODULE_MAIN>
|
||||||
cat <<EOF > $cfg
|
cat <<EOF > $cfg
|
||||||
|
|
@ -16,7 +15,7 @@ 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>$fyang</CLICON_YANG_MAIN_FILE>
|
<CLICON_YANG_MAIN_FILE>$fjukebox</CLICON_YANG_MAIN_FILE>
|
||||||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
<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_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
|
||||||
|
|
@ -26,269 +25,8 @@ cat <<EOF > $cfg
|
||||||
</clixon-config>
|
</clixon-config>
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat <<EOF > $fyang
|
# Common Jukebox spec (fjukebox must be set)
|
||||||
module example-jukebox {
|
. ./jukebox.sh
|
||||||
|
|
||||||
namespace "http://example.com/ns/example-jukebox";
|
|
||||||
prefix "jbox";
|
|
||||||
|
|
||||||
organization "Example, Inc.";
|
|
||||||
contact "support at example.com";
|
|
||||||
description "Example Jukebox Data Model Module.";
|
|
||||||
revision "2016-08-15" {
|
|
||||||
description "Initial version.";
|
|
||||||
reference "example.com document 1-4673.";
|
|
||||||
}
|
|
||||||
|
|
||||||
identity genre {
|
|
||||||
description
|
|
||||||
"Base for all genre types.";
|
|
||||||
}
|
|
||||||
|
|
||||||
// abbreviated list of genre classifications
|
|
||||||
identity alternative {
|
|
||||||
base genre;
|
|
||||||
description
|
|
||||||
"Alternative music.";
|
|
||||||
}
|
|
||||||
identity blues {
|
|
||||||
base genre;
|
|
||||||
description
|
|
||||||
"Blues music.";
|
|
||||||
}
|
|
||||||
identity country {
|
|
||||||
base genre;
|
|
||||||
description
|
|
||||||
"Country music.";
|
|
||||||
}
|
|
||||||
identity jazz {
|
|
||||||
base genre;
|
|
||||||
description
|
|
||||||
"Jazz music.";
|
|
||||||
}
|
|
||||||
identity pop {
|
|
||||||
base genre;
|
|
||||||
description
|
|
||||||
"Pop music.";
|
|
||||||
}
|
|
||||||
identity rock {
|
|
||||||
base genre;
|
|
||||||
description
|
|
||||||
"Rock music.";
|
|
||||||
}
|
|
||||||
|
|
||||||
container jukebox {
|
|
||||||
presence
|
|
||||||
"An empty container indicates that the jukebox
|
|
||||||
service is available.";
|
|
||||||
|
|
||||||
description
|
|
||||||
"Represents a 'jukebox' resource, with a library, playlists,
|
|
||||||
and a 'play' operation.";
|
|
||||||
|
|
||||||
container library {
|
|
||||||
|
|
||||||
description
|
|
||||||
"Represents the 'jukebox' library resource.";
|
|
||||||
|
|
||||||
list artist {
|
|
||||||
key name;
|
|
||||||
description
|
|
||||||
"Represents one 'artist' resource within the
|
|
||||||
'jukebox' library resource.";
|
|
||||||
|
|
||||||
leaf name {
|
|
||||||
type string {
|
|
||||||
length "1 .. max";
|
|
||||||
}
|
|
||||||
description
|
|
||||||
"The name of the artist.";
|
|
||||||
}
|
|
||||||
|
|
||||||
list album {
|
|
||||||
key name;
|
|
||||||
description
|
|
||||||
"Represents one 'album' resource within one
|
|
||||||
'artist' resource, within the jukebox library.";
|
|
||||||
|
|
||||||
leaf name {
|
|
||||||
type string {
|
|
||||||
length "1 .. max";
|
|
||||||
}
|
|
||||||
description
|
|
||||||
"The name of the album.";
|
|
||||||
}
|
|
||||||
|
|
||||||
leaf genre {
|
|
||||||
type identityref { base genre; }
|
|
||||||
description
|
|
||||||
"The genre identifying the type of music on
|
|
||||||
the album.";
|
|
||||||
}
|
|
||||||
|
|
||||||
leaf year {
|
|
||||||
type uint16 {
|
|
||||||
range "1900 .. max";
|
|
||||||
}
|
|
||||||
description
|
|
||||||
"The year the album was released.";
|
|
||||||
}
|
|
||||||
|
|
||||||
container admin {
|
|
||||||
description
|
|
||||||
"Administrative information for the album.";
|
|
||||||
|
|
||||||
leaf label {
|
|
||||||
type string;
|
|
||||||
description
|
|
||||||
"The label that released the album.";
|
|
||||||
}
|
|
||||||
leaf catalogue-number {
|
|
||||||
type string;
|
|
||||||
description
|
|
||||||
"The album's catalogue number.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
list song {
|
|
||||||
key name;
|
|
||||||
description
|
|
||||||
"Represents one 'song' resource within one
|
|
||||||
'album' resource, within the jukebox library.";
|
|
||||||
|
|
||||||
leaf name {
|
|
||||||
type string {
|
|
||||||
length "1 .. max";
|
|
||||||
}
|
|
||||||
description
|
|
||||||
"The name of the song.";
|
|
||||||
}
|
|
||||||
|
|
||||||
leaf location {
|
|
||||||
type string;
|
|
||||||
mandatory true;
|
|
||||||
description
|
|
||||||
"The file location string of the
|
|
||||||
media file for the song.";
|
|
||||||
}
|
|
||||||
leaf format {
|
|
||||||
type string;
|
|
||||||
description
|
|
||||||
"An identifier string for the media type
|
|
||||||
for the file associated with the
|
|
||||||
'location' leaf for this entry.";
|
|
||||||
}
|
|
||||||
leaf length {
|
|
||||||
type uint32;
|
|
||||||
units "seconds";
|
|
||||||
description
|
|
||||||
"The duration of this song in seconds.";
|
|
||||||
}
|
|
||||||
} // end list 'song'
|
|
||||||
} // end list 'album'
|
|
||||||
} // end list 'artist'
|
|
||||||
|
|
||||||
leaf artist-count {
|
|
||||||
type uint32;
|
|
||||||
units "artists";
|
|
||||||
config false;
|
|
||||||
description
|
|
||||||
"Number of artists in the library.";
|
|
||||||
}
|
|
||||||
leaf album-count {
|
|
||||||
type uint32;
|
|
||||||
units "albums";
|
|
||||||
config false;
|
|
||||||
description
|
|
||||||
"Number of albums in the library.";
|
|
||||||
}
|
|
||||||
leaf song-count {
|
|
||||||
type uint32;
|
|
||||||
units "songs";
|
|
||||||
config false;
|
|
||||||
description
|
|
||||||
"Number of songs in the library.";
|
|
||||||
}
|
|
||||||
} // end library
|
|
||||||
|
|
||||||
list playlist {
|
|
||||||
key name;
|
|
||||||
description
|
|
||||||
"Example configuration data resource.";
|
|
||||||
|
|
||||||
leaf name {
|
|
||||||
type string;
|
|
||||||
description
|
|
||||||
"The name of the playlist.";
|
|
||||||
}
|
|
||||||
leaf description {
|
|
||||||
type string;
|
|
||||||
description
|
|
||||||
"A comment describing the playlist.";
|
|
||||||
}
|
|
||||||
list song {
|
|
||||||
key index;
|
|
||||||
ordered-by user;
|
|
||||||
|
|
||||||
description
|
|
||||||
"Example nested configuration data resource.";
|
|
||||||
|
|
||||||
leaf index { // not really needed
|
|
||||||
type uint32;
|
|
||||||
description
|
|
||||||
"An arbitrary integer index for this playlist song.";
|
|
||||||
}
|
|
||||||
leaf id {
|
|
||||||
type instance-identifier;
|
|
||||||
mandatory true;
|
|
||||||
description
|
|
||||||
"Song identifier. Must identify an instance of
|
|
||||||
/jukebox/library/artist/album/song/name.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
container player {
|
|
||||||
description
|
|
||||||
"Represents the jukebox player resource.";
|
|
||||||
|
|
||||||
leaf gap {
|
|
||||||
type decimal64 {
|
|
||||||
fraction-digits 1;
|
|
||||||
range "0.0 .. 2.0";
|
|
||||||
}
|
|
||||||
units "tenths of seconds";
|
|
||||||
description
|
|
||||||
"Time gap between each song.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rpc play {
|
|
||||||
description
|
|
||||||
"Control function for the jukebox player.";
|
|
||||||
input {
|
|
||||||
leaf playlist {
|
|
||||||
type string;
|
|
||||||
mandatory true;
|
|
||||||
description
|
|
||||||
"The playlist name.";
|
|
||||||
}
|
|
||||||
leaf song-number {
|
|
||||||
type uint32;
|
|
||||||
mandatory true;
|
|
||||||
description
|
|
||||||
"Song number in playlist to play.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
leaf-list extra{
|
|
||||||
type string;
|
|
||||||
ordered-by user;
|
|
||||||
description "Extra added to test ordered-by user inserts on leaf-lists";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
new "test params: -f $cfg"
|
new "test params: -f $cfg"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -88,65 +88,64 @@ wait_backend
|
||||||
wait_restconf
|
wait_restconf
|
||||||
|
|
||||||
new "restconf PUT add whole list entry"
|
new "restconf PUT add whole list entry"
|
||||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y -d {"list:a":{"b":"x","c":"y","nonkey":"0"}}' 0 ''
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"x","c":"y","nonkey":"0"}}')" 0 ''
|
||||||
|
|
||||||
new "restconf PUT add whole list entry XML"
|
new "restconf PUT add whole list entry XML"
|
||||||
expecteq "$(curl -s -X PUT -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '<a xmlns="urn:example:clixon"><b>xx</b><c>xy</c><nonkey>0</nonkey></a>' http://localhost/restconf/data/list:c/a=xx,xy)" 0 ''
|
expecteq "$(curl -s -X PUT -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '<a xmlns="urn:example:clixon"><b>xx</b><c>xy</c><nonkey>0</nonkey></a>' http://localhost/restconf/data/list:c/a=xx,xy)" 0 ''
|
||||||
|
|
||||||
|
|
||||||
new "restconf PUT change whole list entry (same keys)"
|
new "restconf PUT change whole list entry (same keys)"
|
||||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y -d {"list:a":{"b":"x","c":"y","nonkey":"z"}}' 0 ''
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"x","c":"y","nonkey":"z"}}')" 0 ''
|
||||||
|
|
||||||
new "restconf PUT change whole list entry (no namespace)(expect fail)"
|
new "restconf PUT change whole list entry (no namespace)(expect fail)"
|
||||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y -d {"a":{"b":"x","c":"y","nonkey":"z"}}' 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Data is not prefixed with matching namespace"}}}'
|
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y -d '{"a":{"b":"x","c":"y","nonkey":"z"}}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Data is not prefixed with matching namespace"}}}
'
|
||||||
|
|
||||||
new "restconf PUT change list entry (wrong keys)(expect fail)"
|
new "restconf PUT change list entry (wrong keys)(expect fail)"
|
||||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y -d {"list:a":{"b":"y","c":"x"}}' 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}'
|
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"y","c":"x"}}')" 0 '412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}
'
|
||||||
|
|
||||||
new "restconf PUT change list entry (wrong keys)(expect fail) XML"
|
new "restconf PUT change list entry (wrong keys)(expect fail) XML"
|
||||||
expecteq "$(curl -s -X PUT -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '<a xmlns="urn:example:clixon"><b>xy</b><c>xz</c><nonkey>0</nonkey></a>' http://localhost/restconf/data/list:c/a=xx,xy)" 0 '<errors xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>api-path keys do not match data keys</error-message></error></errors>
'
|
expecteq "$(curl -s -X PUT -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '<a xmlns="urn:example:clixon"><b>xy</b><c>xz</c><nonkey>0</nonkey></a>' http://localhost/restconf/data/list:c/a=xx,xy)" 0 '<errors xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>api-path keys do not match data keys</error-message></error></errors>
'
|
||||||
|
|
||||||
new "restconf PUT change list entry (just one key)(expect fail)"
|
new "restconf PUT change list entry (just one key)(expect fail)"
|
||||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x -d {"list:a":{"b":"x"}}' 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}}
'
|
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x -d '{"list:a":{"b":"x"}}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}}
'
|
||||||
|
|
||||||
new "restconf PUT sub non-key"
|
new "restconf PUT sub non-key"
|
||||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/nonkey -d {"list:nonkey":"u"}' 0 ''
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/nonkey -d '{"list:nonkey":"u"}')" 0 ''
|
||||||
|
|
||||||
new "restconf PUT sub key same value"
|
new "restconf PUT sub key same value"
|
||||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/b -d {"list:b":"x"}' 0 ''
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/b -d '{"list:b":"x"}')" 0 ''
|
||||||
|
|
||||||
new "restconf PUT just key other value (should fail)ZX"
|
new "restconf PUT just key other value (should fail)"
|
||||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x/b -d {"b":"y"}' 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}}'
|
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x/b -d '{"b":"y"}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}}'
|
||||||
|
|
||||||
new "restconf PUT add leaf-list entry"
|
new "restconf PUT add leaf-list entry"
|
||||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/d=x -d {"list:d":"x"}' 0 ''
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/d=x -d '{"list:d":"x"}')" 0 ''
|
||||||
|
|
||||||
new "restconf PUT change leaf-list entry (expect fail)"
|
new "restconf PUT change leaf-list entry (expect fail)"
|
||||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/d=x -d {"list:d":"y"}' 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}'
|
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/d=x -d '{"list:d":"y"}')" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}'
|
||||||
|
|
||||||
new "restconf PUT list-list"
|
new "restconf PUT list-list"
|
||||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/e=z -d {"list:e":{"f":"z","nonkey":"0"}}' 0 ''
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/e=z -d '{"list:e":{"f":"z","nonkey":"0"}}')" 0 ''
|
||||||
|
|
||||||
new "restconf PUT change list-lst entry (wrong keys)(expect fail)"
|
new "restconf PUT change list-lst entry (wrong keys)(expect fail)"
|
||||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/e=z -d {"list:e":{"f":"wrong","nonley":"0"}}' 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}'
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/e=z -d '{"list:e":{"f":"wrong","nonley":"0"}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}
'
|
||||||
|
|
||||||
new "restconf PUT list-list sub non-key"
|
new "restconf PUT list-list sub non-key"
|
||||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/e=z/nonkey -d {"list:nonkey":"u"}' 0 ''
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/e=z/nonkey -d '{"list:nonkey":"u"}')" 0 ''
|
||||||
|
|
||||||
new "restconf PUT list-list single first key"
|
new "restconf PUT list-list single first key"
|
||||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x/e=z/f -d {"f":"z"}' 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}}'
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x/e=z/f -d '{"f":"z"}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}}
'
|
||||||
|
|
||||||
new "restconf PUT list-list just key ok"
|
new "restconf PUT list-list just key ok"
|
||||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/e=z/f -d {"list:f":"z"}' 0 ''
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/e=z/f -d '{"list:f":"z"}')" 0 ''
|
||||||
|
|
||||||
new "restconf PUT list-list just key just key wrong value (should fail)"
|
new "restconf PUT list-list just key just key wrong value (should fail)"
|
||||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/e=z/f -d {"list:f":"wrong"}' 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}'
|
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/e=z/f -d '{"list:f":"wrong"}')" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}
'
|
||||||
|
|
||||||
new "restconf PUT add list+leaf-list entry"
|
new "restconf PUT add list+leaf-list entry"
|
||||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/f=u -d {"list:f":"u"}' 0 ''
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/f=u -d '{"list:f":"u"}')" 0 ''
|
||||||
|
|
||||||
new "restconf PUT change list+leaf-list entry (expect fail)"
|
new "restconf PUT change list+leaf-list entry (expect fail)"
|
||||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/f=u -d {"list:f":"w"}' 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}'
|
expectpart "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/f=u -d '{"list:f":"w"}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}'
|
||||||
|
|
||||||
|
|
||||||
new "Kill restconf daemon"
|
new "Kill restconf daemon"
|
||||||
|
|
|
||||||
69
test/test_restconf_patch.sh
Executable file
69
test/test_restconf_patch.sh
Executable file
|
|
@ -0,0 +1,69 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Restconf RFC8040 plain patch
|
||||||
|
|
||||||
|
# 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_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
|
||||||
|
<CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR>
|
||||||
|
<CLICON_YANG_MAIN_FILE>$fjukebox</CLICON_YANG_MAIN_FILE>
|
||||||
|
<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>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||||
|
</clixon-config>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Common Jukebox spec (fjukebox must be set)
|
||||||
|
. ./jukebox.sh
|
||||||
|
|
||||||
|
new "test params: -f $cfg"
|
||||||
|
|
||||||
|
if [ $BE -ne 0 ]; then
|
||||||
|
new "kill old backend"
|
||||||
|
sudo clixon_backend -zf $cfg
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
err
|
||||||
|
fi
|
||||||
|
sudo pkill clixon_backend # to be sure
|
||||||
|
new "start backend -s init -f $cfg"
|
||||||
|
start_backend -s init -f $cfg
|
||||||
|
fi
|
||||||
|
|
||||||
|
new "kill old restconf daemon"
|
||||||
|
sudo pkill -u www-data -f "/www-data/clixon_restconf"
|
||||||
|
|
||||||
|
new "start restconf daemon"
|
||||||
|
start_restconf -f $cfg
|
||||||
|
|
||||||
|
new "waiting"
|
||||||
|
wait_backend
|
||||||
|
wait_restconf
|
||||||
|
|
||||||
|
|
||||||
|
new "Kill restconf daemon"
|
||||||
|
stop_restconf
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -72,13 +72,13 @@ testrun(){
|
||||||
wait_restconf
|
wait_restconf
|
||||||
|
|
||||||
new "restconf put 42"
|
new "restconf put 42"
|
||||||
expecteq "$(curl -s -X PUT http://localhost/restconf/data/example:x/y=42 -d '{"example:y":{"a":"42","b":"42"}}')" 0 ""
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:x/y=42 -d '{"example:y":{"a":"42","b":"42"}}')" 0 ""
|
||||||
|
|
||||||
new "restconf put 99"
|
new "restconf put 99"
|
||||||
expecteq "$(curl -s -X PUT http://localhost/restconf/data/example:x/y=99 -d '{"example:y":{"a":"99","b":"99"}}')" 0 ""
|
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:x/y=99 -d '{"example:y":{"a":"99","b":"99"}}')" 0 ""
|
||||||
|
|
||||||
new "restconf post 123"
|
new "restconf post 123"
|
||||||
expecteq "$(curl -s -X POST http://localhost/restconf/data/example:x -d '{"example:y":{"a":"123","b":"123"}}')" 0 ""
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:x -d '{"example:y":{"a":"123","b":"123"}}')" 0 ""
|
||||||
|
|
||||||
new "restconf delete 42"
|
new "restconf delete 42"
|
||||||
expecteq "$(curl -s -X DELETE http://localhost/restconf/data/example:x/y=42)" 0 ""
|
expecteq "$(curl -s -X DELETE http://localhost/restconf/data/example:x/y=42)" 0 ""
|
||||||
|
|
|
||||||
|
|
@ -70,36 +70,35 @@ new "netconf empty rpc"
|
||||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><empty xmlns="urn:example:clixon"/></rpc>]]>]]>' '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><ok/></rpc-reply>]]>]]>$'
|
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><empty xmlns="urn:example:clixon"/></rpc>]]>]]>' '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><ok/></rpc-reply>]]>]]>$'
|
||||||
|
|
||||||
new "restconf example rpc json/json default - no http media headers"
|
new "restconf example rpc json/json default - no http media headers"
|
||||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"x":0}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"0","y":"42"}}
|
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":0}}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 200 OK' 'Content-Type: application/yang-data+json' '{"clixon-example:output":{"x":"0","y":"42"}}
|
||||||
'
|
'
|
||||||
|
|
||||||
new "restconf example rpc json/json change y default"
|
new "restconf example rpc json/json change y default"
|
||||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"x":"0","y":"99"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"0","y":"99"}}
|
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"0","y":"99"}}' http://localhost/restconf/operations/clixon-example:example)" 0 'Content-Type: application/yang-data+json' '{"clixon-example:output":{"x":"0","y":"99"}}
|
||||||
'
|
'
|
||||||
|
|
||||||
new "restconf example rpc json/json"
|
new "restconf example rpc json/json"
|
||||||
# XXX example:input example:output
|
# XXX example:input example:output
|
||||||
expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+json' -H 'Accept: application/yang-data+json' -d '{"clixon-example:input":{"x":"0"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"0","y":"42"}}
|
expectpart "$(curl -is -X POST -H 'Content-Type: application/yang-data+json' -H 'Accept: application/yang-data+json' -d '{"clixon-example:input":{"x":"0"}}' http://localhost/restconf/operations/clixon-example:example)" 0 'Content-Type: application/yang-data+json' '{"clixon-example:output":{"x":"0","y":"42"}}
|
||||||
'
|
'
|
||||||
|
|
||||||
new "restconf example rpc xml/json"
|
new "restconf example rpc xml/json"
|
||||||
expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+json' -d '<input xmlns="urn:example:clixon"><x>0</x></input>' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"0","y":"42"}}
|
expectpart "$(curl -is -X POST -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+json' -d '<input xmlns="urn:example:clixon"><x>0</x></input>' http://localhost/restconf/operations/clixon-example:example)" 0 'Content-Type: application/yang-data+json' '{"clixon-example:output":{"x":"0","y":"42"}}
|
||||||
'
|
'
|
||||||
|
|
||||||
new "restconf example rpc json/xml"
|
new "restconf example rpc json/xml"
|
||||||
expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+json' -H 'Accept: application/yang-data+xml' -d '{"clixon-example:input":{"x":"0"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '<output xmlns="urn:example:clixon"><x>0</x><y>42</y></output>
|
expectpart "$(curl -is -X POST -H 'Content-Type: application/yang-data+json' -H 'Accept: application/yang-data+xml' -d '{"clixon-example:input":{"x":"0"}}' http://localhost/restconf/operations/clixon-example:example)" 0 'Content-Type: application/yang-data+xml' '<output xmlns="urn:example:clixon"><x>0</x><y>42</y></output>
|
||||||
'
|
'
|
||||||
|
|
||||||
new "restconf example rpc xml/xml"
|
new "restconf example rpc xml/xml"
|
||||||
expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '<input xmlns="urn:example:clixon"><x>0</x></input>' http://localhost/restconf/operations/clixon-example:example)" 0 '<output xmlns="urn:example:clixon"><x>0</x><y>42</y></output>
|
expectpart "$(curl -is -X POST -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '<input xmlns="urn:example:clixon"><x>0</x></input>' http://localhost/restconf/operations/clixon-example:example)" 0 'Content-Type: application/yang-data+xml' '<output xmlns="urn:example:clixon"><x>0</x><y>42</y></output>
|
||||||
'
|
'
|
||||||
|
|
||||||
new "restconf example rpc xml in w json encoding (expect fail)"
|
new "restconf example rpc xml in w json encoding (expect fail)"
|
||||||
expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+json' -H 'Accept: application/yang-data+xml' -d '<input xmlns="urn:example:clixon"><x>0</x></input>' http://localhost/restconf/operations/clixon-example:example)" 0 "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>rpc</error-type><error-tag>malformed-message</error-tag><error-severity>error</error-severity><error-message> on line 1: syntax error at or before: '<'</error-message></error></errors>
"
|
expectpart "$(curl -is -X POST -H 'Content-Type: application/yang-data+json' -H 'Accept: application/yang-data+xml' -d '<input xmlns="urn:example:clixon"><x>0</x></input>' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Reques' "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>rpc</error-type><error-tag>malformed-message</error-tag><error-severity>error</error-severity><error-message> on line 1: syntax error at or before: '<'</error-message></error></errors>
"
|
||||||
|
|
||||||
|
|
||||||
new "restconf example rpc json in xml encoding (expect fail)"
|
new "restconf example rpc json in xml encoding (expect fail)"
|
||||||
expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '{"clixon-example:input":{"x":"0"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '<errors xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><error><error-type>rpc</error-type><error-tag>malformed-message</error-tag><error-severity>error</error-severity><error-message>xml_parse: line 0: syntax error: at or before: "</error-message></error></errors>
'
|
expectpart "$(curl -is -X POST -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '{"clixon-example:input":{"x":"0"}}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Reques' '<errors xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><error><error-type>rpc</error-type><error-tag>malformed-message</error-tag><error-severity>error</error-severity><error-message>xml_parse: line 0: syntax error: at or before: "</error-message></error></errors>
'
|
||||||
|
|
||||||
new "netconf example rpc"
|
new "netconf example rpc"
|
||||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><example xmlns="urn:example:clixon"><x>0</x></example></rpc>]]>]]>' '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><x xmlns="urn:example:clixon">0</x><y xmlns="urn:example:clixon">42</y></rpc-reply>]]>]]>$'
|
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><example xmlns="urn:example:clixon"><x>0</x></example></rpc>]]>]]>' '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><x xmlns="urn:example:clixon">0</x><y xmlns="urn:example:clixon">42</y></rpc-reply>]]>]]>$'
|
||||||
|
|
@ -107,7 +106,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netco
|
||||||
# 2. Then error cases
|
# 2. Then error cases
|
||||||
#
|
#
|
||||||
new "restconf empty rpc with null input"
|
new "restconf empty rpc with null input"
|
||||||
ret=$(curl -is -X POST -d '{"clixon-example:input":null}' http://localhost/restconf/operations/clixon-example:empty)
|
ret=$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":null}' http://localhost/restconf/operations/clixon-example:empty)
|
||||||
expect="204 No Content"
|
expect="204 No Content"
|
||||||
match=`echo $ret | grep -EZo "$expect"`
|
match=`echo $ret | grep -EZo "$expect"`
|
||||||
if [ -z "$match" ]; then
|
if [ -z "$match" ]; then
|
||||||
|
|
@ -115,11 +114,11 @@ if [ -z "$match" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "restconf empty rpc with input x"
|
new "restconf empty rpc with input x"
|
||||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"x":0}}' http://localhost/restconf/operations/clixon-example:empty)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"x"},"error-severity":"error"}}}
'
|
expectpart "$(curl -iss -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":0}}' http://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"x"},"error-severity":"error"}}}
'
|
||||||
|
|
||||||
# cornercase: optional has yang input/output sections but test without body
|
# cornercase: optional has yang input/output sections but test without body
|
||||||
new "restconf optional rpc with null input and output"
|
new "restconf optional rpc with null input and output"
|
||||||
ret=$(curl -is -X POST -d '{"clixon-example:input":null}' http://localhost/restconf/operations/clixon-example:optional)
|
ret=$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":null}' http://localhost/restconf/operations/clixon-example:optional)
|
||||||
expect="204 No Content"
|
expect="204 No Content"
|
||||||
match=`echo $ret | grep -EZo "$expect"`
|
match=`echo $ret | grep -EZo "$expect"`
|
||||||
if [ -z "$match" ]; then
|
if [ -z "$match" ]; then
|
||||||
|
|
@ -127,16 +126,16 @@ if [ -z "$match" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "restconf omit mandatory"
|
new "restconf omit mandatory"
|
||||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":null}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"x"},"error-severity":"error","error-message":"Mandatory variable"}}}
'
|
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":null}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"x"},"error-severity":"error","error-message":"Mandatory variable"}}}
'
|
||||||
|
|
||||||
new "restconf add extra"
|
new "restconf add extra"
|
||||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"x":"0","extra":"0"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error"}}}
'
|
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"0","extra":"0"}}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error"}}}
'
|
||||||
|
|
||||||
new "restconf wrong method"
|
new "restconf wrong method"
|
||||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"x":"0"}}' http://localhost/restconf/operations/clixon-example:wrong)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"wrong"},"error-severity":"error","error-message":"RPC not defined"}}}
'
|
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"0"}}' http://localhost/restconf/operations/clixon-example:wrong)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"wrong"},"error-severity":"error","error-message":"RPC not defined"}}}
'
|
||||||
|
|
||||||
new "restconf example missing input"
|
new "restconf example missing input"
|
||||||
expecteq "$(curl -s -X POST -d '{"clixon-example:input":null}' http://localhost/restconf/operations/ietf-netconf:edit-config)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"target"},"error-severity":"error","error-message":"Mandatory variable"}}}
'
|
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":null}' http://localhost/restconf/operations/ietf-netconf:edit-config)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"target"},"error-severity":"error","error-message":"Mandatory variable"}}}
'
|
||||||
|
|
||||||
new "netconf kill-session missing session-id mandatory"
|
new "netconf kill-session missing session-id mandatory"
|
||||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><kill-session/></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>missing-element</error-tag><error-info><bad-element>session-id</bad-element></error-info><error-severity>error</error-severity><error-message>Mandatory variable</error-message></rpc-error></rpc-reply>]]>]]>$'
|
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><kill-session/></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>missing-element</error-tag><error-info><bad-element>session-id</bad-element></error-info><error-severity>error</error-severity><error-message>Mandatory variable</error-message></rpc-error></rpc-reply>]]>]]>$'
|
||||||
|
|
|
||||||
|
|
@ -204,16 +204,16 @@ expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<
|
||||||
|
|
||||||
# Now same with restconf
|
# Now same with restconf
|
||||||
new "restconf edit main"
|
new "restconf edit main"
|
||||||
expectfn 'curl -s -i -X POST http://localhost/restconf/data -d {"main:main":{"x":"foo","ext":"foo"}}' 0 'HTTP/1.1 201 Created'
|
expectpart "$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data -d '{"main:main":{"x":"foo","ext":"foo"}}')" 0 'HTTP/1.1 201 Created'
|
||||||
|
|
||||||
new "restconf edit sub1"
|
new "restconf edit sub1"
|
||||||
expectfn 'curl -s -i -X POST http://localhost/restconf/data -d {"main:sub1":{"x":"foo","ext1":"foo"}}' 0 'HTTP/1.1 201 Created'
|
expectpart "$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data -d '{"main:sub1":{"x":"foo","ext1":"foo"}}')" 0 'HTTP/1.1 201 Created'
|
||||||
|
|
||||||
new "restconf edit sub2"
|
new "restconf edit sub2"
|
||||||
expectfn 'curl -s -i -X POST http://localhost/restconf/data -d {"main:sub2":{"x":"foo","ext2":"foo"}}' 0 'HTTP/1.1 201 Created'
|
expectpart "$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data -d '{"main:sub2":{"x":"foo","ext2":"foo"}}')" 0 'HTTP/1.1 201 Created'
|
||||||
|
|
||||||
new "restconf check main/sub1/sub2 contents"
|
new "restconf check main/sub1/sub2 contents"
|
||||||
expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data":{"main:main":{"ext":"foo","x":"foo"},"main:sub1":{"ext1":"foo","x":"foo"},"main:sub2":{"ext2":"foo","x":"foo"}}}'
|
expectpart "$(curl -s -X GET http://localhost/restconf/data)" 0 '{"data":{"main:main":{"ext":"foo","x":"foo"},"main:sub1":{"ext1":"foo","x":"foo"},"main:sub2":{"ext2":"foo","x":"foo"}}}'
|
||||||
|
|
||||||
new "Kill restconf daemon"
|
new "Kill restconf daemon"
|
||||||
stop_restconf
|
stop_restconf
|
||||||
|
|
|
||||||
|
|
@ -102,14 +102,14 @@ new "netconf discard-changes"
|
||||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "restconf set x in example1"
|
new "restconf set x in example1"
|
||||||
expecteq "$(curl -s -X POST -d '{"example1:x":42}' http://localhost/restconf/data)" 0 ''
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example1:x":42}' http://localhost/restconf/data)" 0 ''
|
||||||
|
|
||||||
new "restconf get config example1"
|
new "restconf get config example1"
|
||||||
expecteq "$(curl -s -X GET http://localhost/restconf/data/example1:x)" 0 '{"example1:x":42}
|
expecteq "$(curl -s -X GET http://localhost/restconf/data/example1:x)" 0 '{"example1:x":42}
|
||||||
'
|
'
|
||||||
|
|
||||||
new "restconf set x in example2"
|
new "restconf set x in example2"
|
||||||
expecteq "$(curl -s -X POST -d '{"example2:x":{"y":99}}' http://localhost/restconf/data)" 0 ''
|
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example2:x":{"y":99}}' http://localhost/restconf/data)" 0 ''
|
||||||
|
|
||||||
# XXX GET ../example1:x is translated to select=/x which gets both example1&2
|
# XXX GET ../example1:x is translated to select=/x which gets both example1&2
|
||||||
#new "restconf get config example1"
|
#new "restconf get config example1"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue