RESTCONF PATCH (plain patch) is supported according to RFC 8040 4.6.1
This commit is contained in:
parent
bb2eaa3cc7
commit
46b6a8008a
15 changed files with 375 additions and 474 deletions
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
### Major New features
|
||||
* Restconf RFC 8040 increased feature compliance
|
||||
* RESTCONF PATCH (plain patch) is supported according to RFC 8040 4.6.1
|
||||
* RESTCONF "insert" and "point" query parameters supported
|
||||
* Applies to ordered-by-user leaf and leaf-lists
|
||||
* RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns:
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@ APPSRC = restconf_main.c
|
|||
APPSRC += restconf_methods.c
|
||||
APPSRC += restconf_methods_post.c
|
||||
APPSRC += restconf_methods_get.c
|
||||
APPSRC += restconf_methods_patch.c
|
||||
APPSRC += restconf_stream.c
|
||||
APPOBJ = $(APPSRC:.c=.o)
|
||||
|
||||
|
|
|
|||
|
|
@ -171,6 +171,21 @@ restconf_media_int2str(restconf_media media)
|
|||
return clicon_int2str(http_media_map, media);
|
||||
}
|
||||
|
||||
/*! Return media_in from Content-Type, -1 if not found or unrecognized
|
||||
*/
|
||||
restconf_media
|
||||
restconf_content_type(FCGX_Request *r)
|
||||
{
|
||||
char *str;
|
||||
restconf_media m;
|
||||
|
||||
if ((str = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp)) == NULL)
|
||||
return -1;
|
||||
if ((m = restconf_media_str2int(str)) == -1)
|
||||
return -1;
|
||||
return m;
|
||||
}
|
||||
|
||||
/*! HTTP error 400
|
||||
* @param[in] r Fastcgi request handle
|
||||
*/
|
||||
|
|
@ -179,9 +194,9 @@ restconf_badrequest(FCGX_Request *r)
|
|||
{
|
||||
char *path;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
|
||||
FCGX_FPrintF(r->out, "Status: 400\r\n"); /* 400 bad request */
|
||||
FCGX_SetExitStatus(400, r->out);
|
||||
FCGX_FPrintF(r->out, "Status: 400 Bad Request\r\n"); /* 400 bad request */
|
||||
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
|
||||
FCGX_FPrintF(r->out, "<h1>Clixon Bad request/h1>\n");
|
||||
FCGX_FPrintF(r->out, "The requested URL %s or data is in some way badly formed.\n",
|
||||
|
|
@ -197,9 +212,9 @@ restconf_unauthorized(FCGX_Request *r)
|
|||
{
|
||||
char *path;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
|
||||
FCGX_FPrintF(r->out, "Status: 401\r\n"); /* 401 unauthorized */
|
||||
FCGX_SetExitStatus(401, r->out);
|
||||
FCGX_FPrintF(r->out, "Status: 401 Unauthorized\r\n"); /* 401 unauthorized */
|
||||
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
|
||||
FCGX_FPrintF(r->out, "<error-tag>access-denied</error-tag>\n");
|
||||
FCGX_FPrintF(r->out, "The requested URL %s was unauthorized.\n", path);
|
||||
|
|
@ -214,9 +229,9 @@ restconf_forbidden(FCGX_Request *r)
|
|||
{
|
||||
char *path;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
|
||||
FCGX_FPrintF(r->out, "Status: 403\r\n"); /* 403 forbidden */
|
||||
FCGX_SetExitStatus(403, r->out);
|
||||
FCGX_FPrintF(r->out, "Status: 403 Forbidden\r\n"); /* 403 forbidden */
|
||||
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
|
||||
FCGX_FPrintF(r->out, "<h1>Forbidden</h1>\n");
|
||||
FCGX_FPrintF(r->out, "The requested URL %s was forbidden.\n", path);
|
||||
|
|
@ -231,10 +246,9 @@ restconf_notfound(FCGX_Request *r)
|
|||
{
|
||||
char *path;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
|
||||
FCGX_FPrintF(r->out, "Status: 404\r\n"); /* 404 not found */
|
||||
|
||||
FCGX_SetExitStatus(404, r->out);
|
||||
FCGX_FPrintF(r->out, "Status: 404 Not Found\r\n"); /* 404 not found */
|
||||
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
|
||||
FCGX_FPrintF(r->out, "<h1>Not Found</h1>\n");
|
||||
FCGX_FPrintF(r->out, "Not Found\n");
|
||||
|
|
@ -251,9 +265,9 @@ restconf_notacceptable(FCGX_Request *r)
|
|||
{
|
||||
char *path;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
|
||||
FCGX_FPrintF(r->out, "Status: 406\r\n"); /* 406 not acceptible */
|
||||
FCGX_SetExitStatus(406, r->out);
|
||||
FCGX_FPrintF(r->out, "Status: 406 Not Acceptable\r\n"); /* 406 not acceptible */
|
||||
|
||||
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
|
||||
FCGX_FPrintF(r->out, "<h1>Not Acceptable</h1>\n");
|
||||
|
|
@ -269,8 +283,8 @@ restconf_notacceptable(FCGX_Request *r)
|
|||
int
|
||||
restconf_conflict(FCGX_Request *r)
|
||||
{
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
FCGX_FPrintF(r->out, "Status: 409\r\n"); /* 409 Conflict */
|
||||
FCGX_SetExitStatus(409, r->out);
|
||||
FCGX_FPrintF(r->out, "Status: 409 Conflict\r\n"); /* 409 Conflict */
|
||||
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
|
||||
FCGX_FPrintF(r->out, "<h1>Data resource already exists</h1>\n");
|
||||
return 0;
|
||||
|
|
@ -282,7 +296,6 @@ restconf_conflict(FCGX_Request *r)
|
|||
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");
|
||||
|
|
@ -300,7 +313,7 @@ restconf_internal_server_error(FCGX_Request *r)
|
|||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
|
||||
FCGX_FPrintF(r->out, "Status: 500\r\n"); /* 500 internal server error */
|
||||
FCGX_FPrintF(r->out, "Status: 500 Internal Server Error\r\n"); /* 500 internal server error */
|
||||
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
|
||||
FCGX_FPrintF(r->out, "<h1>Internal server error when accessing %s</h1>\n", path);
|
||||
return 0;
|
||||
|
|
@ -313,7 +326,7 @@ int
|
|||
restconf_notimplemented(FCGX_Request *r)
|
||||
{
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
FCGX_FPrintF(r->out, "Status: 501\r\n");
|
||||
FCGX_FPrintF(r->out, "Status: 501 Not Implemented\r\n");
|
||||
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
|
||||
FCGX_FPrintF(r->out, "<h1>Not Implemented/h1>\n");
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ const char *restconf_code2reason(int code);
|
|||
|
||||
const restconf_media restconf_media_str2int(char *media);
|
||||
const char *restconf_media_int2str(restconf_media media);
|
||||
|
||||
restconf_media restconf_content_type(FCGX_Request *r);
|
||||
int restconf_badrequest(FCGX_Request *r);
|
||||
int restconf_unauthorized(FCGX_Request *r);
|
||||
int restconf_forbidden(FCGX_Request *r);
|
||||
|
|
|
|||
|
|
@ -80,7 +80,6 @@
|
|||
#include "restconf_methods.h"
|
||||
#include "restconf_methods_get.h"
|
||||
#include "restconf_methods_post.h"
|
||||
#include "restconf_methods_patch.h"
|
||||
#include "restconf_stream.h"
|
||||
|
||||
/* Command line options to be passed to getopt(3) */
|
||||
|
|
@ -113,7 +112,6 @@ api_data(clicon_handle h,
|
|||
cvec *qvec,
|
||||
char *data,
|
||||
int pretty,
|
||||
restconf_media media_in,
|
||||
restconf_media media_out)
|
||||
{
|
||||
int retval = -1;
|
||||
|
|
@ -129,11 +127,11 @@ api_data(clicon_handle h,
|
|||
else if (strcmp(request_method, "GET")==0)
|
||||
retval = api_data_get(h, r, pcvec, pi, qvec, pretty, media_out);
|
||||
else if (strcmp(request_method, "POST")==0)
|
||||
retval = api_data_post(h, r, api_path, pcvec, pi, qvec, data, pretty, media_in, media_out);
|
||||
retval = api_data_post(h, r, api_path, pcvec, pi, qvec, data, pretty, media_out);
|
||||
else if (strcmp(request_method, "PUT")==0)
|
||||
retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data, pretty, media_in, media_out);
|
||||
retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data, pretty, media_out);
|
||||
else if (strcmp(request_method, "PATCH")==0)
|
||||
retval = api_data_patch(h, r, api_path, pcvec, pi, qvec, data, pretty, media_in, media_out);
|
||||
retval = api_data_patch(h, r, api_path, pcvec, pi, qvec, data, pretty, media_out);
|
||||
else if (strcmp(request_method, "DELETE")==0)
|
||||
retval = api_data_delete(h, r, api_path, pi, pretty, media_out);
|
||||
else
|
||||
|
|
@ -150,7 +148,6 @@ api_data(clicon_handle h,
|
|||
* @param[in] pi Offset, where to start pcvec
|
||||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||
* @param[in] data Stream input data
|
||||
* @param[in] media_in Input media media
|
||||
* @param[in] media_out Output media
|
||||
*/
|
||||
static int
|
||||
|
|
@ -162,7 +159,6 @@ api_operations(clicon_handle h,
|
|||
cvec *qvec,
|
||||
char *data,
|
||||
int pretty,
|
||||
restconf_media media_in,
|
||||
restconf_media media_out)
|
||||
{
|
||||
int retval = -1;
|
||||
|
|
@ -175,7 +171,7 @@ api_operations(clicon_handle h,
|
|||
retval = api_operations_get(h, r, path, pcvec, pi, qvec, data, pretty, media_out);
|
||||
else if (strcmp(request_method, "POST")==0)
|
||||
retval = api_operations_post(h, r, path, pcvec, pi, qvec, data,
|
||||
pretty, media_in, media_out);
|
||||
pretty, media_out);
|
||||
else
|
||||
retval = restconf_notfound(r);
|
||||
return retval;
|
||||
|
|
@ -334,7 +330,6 @@ api_restconf(clicon_handle h,
|
|||
char *data;
|
||||
int authenticated = 0;
|
||||
char *media_str = NULL;
|
||||
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;
|
||||
cbuf *cbret = NULL;
|
||||
|
|
@ -345,19 +340,7 @@ api_restconf(clicon_handle h,
|
|||
path = restconf_uripath(r);
|
||||
query = FCGX_GetParam("QUERY_STRING", r->envp);
|
||||
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
|
||||
/* Get media for input (Content-Type)
|
||||
* This is for methods that have input, such as PUT/POST, etc
|
||||
*/
|
||||
if ((media_str = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp)) == NULL){
|
||||
retval = restconf_unsupported_media(r);
|
||||
goto done;
|
||||
}
|
||||
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
|
||||
|
|
@ -444,12 +427,12 @@ api_restconf(clicon_handle h,
|
|||
}
|
||||
else if (strcmp(method, "data") == 0){ /* restconf, skip /api/data */
|
||||
if (api_data(h, r, path, pcvec, 2, qvec, data,
|
||||
pretty, media_in, media_out) < 0)
|
||||
pretty, media_out) < 0)
|
||||
goto done;
|
||||
}
|
||||
else if (strcmp(method, "operations") == 0){ /* rpc */
|
||||
if (api_operations(h, r, path, pcvec, 2, qvec, data,
|
||||
pretty, media_in, media_out) < 0)
|
||||
pretty, media_out) < 0)
|
||||
goto done;
|
||||
}
|
||||
else if (strcmp(method, "test") == 0)
|
||||
|
|
|
|||
|
|
@ -215,53 +215,24 @@ match_list_keys(yang_stmt *y,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Generic REST PUT method
|
||||
* @param[in] h CLIXON handle
|
||||
* @param[in] r Fastcgi request handle
|
||||
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
|
||||
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
|
||||
* @param[in] pi Offset, where to start pcvec
|
||||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||
* @param[in] data Stream input data
|
||||
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||
* @param[in] media_in Input media media
|
||||
* @param[in] media_out Output media
|
||||
|
||||
* @note restconf PUT is mapped to edit-config replace.
|
||||
* @see RFC8040 Sec 4.5 PUT
|
||||
* @see api_data_post
|
||||
* @example
|
||||
curl -X PUT -d '{"enabled":"false"}' http://127.0.0.1/restconf/data/interfaces/interface=eth1
|
||||
*
|
||||
PUT:
|
||||
A request message-body MUST be present, representing the new data resource, or the server
|
||||
MUST return a "400 Bad Request" status-line.
|
||||
|
||||
...if the PUT request creates a new resource, a "201 Created" status-line is returned.
|
||||
If an existing resource is modified, a "204 No Content" status-line is returned.
|
||||
|
||||
* Netconf: <edit-config> (nc:operation="create/replace")
|
||||
* Note RFC8040 says that if an object is created, 201 is returned, if replaced 204
|
||||
* is returned. But the restconf client does not know if it is replaced or created,
|
||||
* only the server knows that. Solutions:
|
||||
* 1) extend the netconf <ok/> so it returns if created/replaced. But that would lead
|
||||
* to extension of netconf that may hit other places.
|
||||
* 2) Send a get first and see if the resource exists, and then send replace/create.
|
||||
* Will always produce an extra message and the GET may potetnially waste bw.
|
||||
* 3) Try to create first, if that fails (with conflict) then try replace.
|
||||
* --> Best solution and applied here
|
||||
*/
|
||||
int
|
||||
api_data_put(clicon_handle h,
|
||||
FCGX_Request *r,
|
||||
char *api_path0,
|
||||
cvec *pcvec,
|
||||
int pi,
|
||||
cvec *qvec,
|
||||
char *data,
|
||||
int pretty,
|
||||
restconf_media media_in,
|
||||
restconf_media media_out)
|
||||
/*! Common PUT plain PATCH method
|
||||
* Code checks if object exists.
|
||||
* PUT: If it does not, set op to create, otherwise replace
|
||||
* PATCH: If it does not, fail, otherwise replace/merge
|
||||
* @param[in] plain_patch fail if object does not exists AND merge (not replace)
|
||||
*/
|
||||
static int
|
||||
api_data_write(clicon_handle h,
|
||||
FCGX_Request *r,
|
||||
char *api_path0,
|
||||
cvec *pcvec,
|
||||
int pi,
|
||||
cvec *qvec,
|
||||
char *data,
|
||||
int pretty,
|
||||
restconf_media media_in,
|
||||
restconf_media media_out,
|
||||
int plain_patch)
|
||||
{
|
||||
int retval = -1;
|
||||
enum operation_type op;
|
||||
|
|
@ -282,13 +253,15 @@ api_data_put(clicon_handle h,
|
|||
cxobj *xret = NULL;
|
||||
cxobj *xretcom = NULL; /* return from commit */
|
||||
cxobj *xretdis = NULL; /* return from discard-changes */
|
||||
cxobj *xerr = NULL; /* malloced must be freed */
|
||||
cxobj *xe;
|
||||
cxobj *xerr = NULL; /* malloced must be freed */
|
||||
cxobj *xe; /* direct pointer into tree, dont free */
|
||||
char *username;
|
||||
int ret;
|
||||
char *namespace0;
|
||||
char *namespace = NULL;
|
||||
char *dname;
|
||||
int nullspec = 0;
|
||||
char *xpath = NULL;
|
||||
cbuf *cbpath = NULL;
|
||||
|
||||
clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path0);
|
||||
clicon_debug(1, "%s data:\"%s\"", __FUNCTION__, data);
|
||||
|
|
@ -299,6 +272,84 @@ api_data_put(clicon_handle h,
|
|||
api_path=api_path0;
|
||||
for (i=0; i<pi; i++)
|
||||
api_path = index(api_path+1, '/');
|
||||
/* Check if object exists in backend.
|
||||
* Translate api-path to xpath */
|
||||
namespace = NULL;
|
||||
if ((cbpath = cbuf_new()) == NULL)
|
||||
goto done;
|
||||
cprintf(cbpath, "/");
|
||||
if ((ret = api_path2xpath_cvv(pcvec, pi, yspec, cbpath, &namespace)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0)
|
||||
goto done;
|
||||
clicon_err_reset();
|
||||
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
|
||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||
goto done;
|
||||
}
|
||||
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
xpath = cbuf_get(cbpath);
|
||||
|
||||
/* Create text buffer for transfer to backend */
|
||||
if ((cbx = cbuf_new()) == NULL)
|
||||
goto done;
|
||||
/* show done automaticaly by the system, therefore recovery user is used
|
||||
* here */
|
||||
cprintf(cbx, "<rpc username=\"%s\"", NACM_RECOVERY_USER);
|
||||
if (namespace)
|
||||
cprintf(cbx, " xmlns:nc=\"%s\">", NETCONF_BASE_NAMESPACE);
|
||||
cprintf(cbx, "><get-config><source><candidate/></source>");
|
||||
if (namespace)
|
||||
cprintf(cbx, "<nc:filter nc:type=\"xpath\" nc:select=\"%s\" xmlns=\"%s\"/>",
|
||||
xpath, namespace);
|
||||
else /* If xpath != /, this will probably yield an error later */
|
||||
cprintf(cbx, "<filter type=\"xpath\" select=\"%s\"/>", xpath);
|
||||
cprintf(cbx, "</get-config></rpc>");
|
||||
xret = NULL;
|
||||
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0){
|
||||
if (netconf_operation_failed_xml(&xerr, "protocol", 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 0
|
||||
if (debug){
|
||||
cbuf *ccc=cbuf_new();
|
||||
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 ((xe = xpath_first(xret, "/rpc-reply/data")) == NULL ||
|
||||
xml_child_nr(xe) == 0){ /* Object does not exist */
|
||||
if (plain_patch){ /* If the target resource instance does not exist, the server MUST NOT create it. */
|
||||
restconf_badrequest(r);
|
||||
goto ok;
|
||||
}
|
||||
else
|
||||
op = OP_CREATE;
|
||||
}
|
||||
else{
|
||||
if (plain_patch)
|
||||
op = OP_MERGE;
|
||||
else
|
||||
op = OP_REPLACE;
|
||||
}
|
||||
if (xret){
|
||||
xml_free(xret);
|
||||
xret = NULL;
|
||||
}
|
||||
|
||||
/* Create config top-of-tree */
|
||||
if ((xtop = xml_new("config", NULL, NULL)) == NULL)
|
||||
goto done;
|
||||
|
|
@ -322,6 +373,20 @@ api_data_put(clicon_handle h,
|
|||
goto ok;
|
||||
}
|
||||
}
|
||||
/* 4.4.1: The message-body MUST contain exactly one instance of the
|
||||
* expected data resource. (tested again below)
|
||||
*/
|
||||
if (data == NULL || strlen(data) == 0){
|
||||
if (netconf_malformed_message_xml(&xerr, "The message-body MUST contain exactly one instance of the expected data resource") < 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;
|
||||
}
|
||||
|
||||
/* Parse input data as json or xml into xml */
|
||||
switch (media_in){
|
||||
|
|
@ -365,13 +430,18 @@ api_data_put(clicon_handle h,
|
|||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
restconf_unsupported_media(r);
|
||||
goto ok;
|
||||
break;
|
||||
} /* switch media_in */
|
||||
|
||||
/* The message-body MUST contain exactly one instance of the
|
||||
* expected data resource.
|
||||
*/
|
||||
if (xml_child_nr(xdata0) != 1){
|
||||
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
||||
if (netconf_malformed_message_xml(&xerr, "The message-body MUST contain exactly one instance of the expected data resource") < 0)
|
||||
goto done;
|
||||
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
|
||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||
|
|
@ -411,15 +481,17 @@ api_data_put(clicon_handle h,
|
|||
}
|
||||
}
|
||||
|
||||
/* Add operation create as attribute. If that fails with Conflict, then try
|
||||
"replace" */
|
||||
/* Add operation create as attribute. If that fails with Conflict, then
|
||||
* try "replace" (see comment in function header)
|
||||
*/
|
||||
if ((xa = xml_new("operation", xdata, NULL)) == NULL)
|
||||
goto done;
|
||||
xml_type_set(xa, CX_ATTR);
|
||||
xml_prefix_set(xa, NETCONF_BASE_PREFIX);
|
||||
op = OP_CREATE;
|
||||
|
||||
if (xml_value_set(xa, xml_operation2str(op)) < 0)
|
||||
goto done;
|
||||
|
||||
/* Top-of tree, no api-path
|
||||
* Replace xparent with x, ie bottom of api-path with data
|
||||
*/
|
||||
|
|
@ -529,21 +601,17 @@ api_data_put(clicon_handle h,
|
|||
|
||||
/* If we already have that default namespace, remove it in child */
|
||||
if ((xa = xml_find_type(xdata, NULL, "xmlns", CX_ATTR)) != NULL){
|
||||
if (xml2ns(xparent, NULL, &namespace0) < 0)
|
||||
if (xml2ns(xparent, NULL, &namespace) < 0)
|
||||
goto done;
|
||||
/* Set xmlns="" default namespace attribute (if diff from default) */
|
||||
if (strcmp(namespace0, xml_value(xa))==0)
|
||||
if (strcmp(namespace, xml_value(xa))==0)
|
||||
xml_purge(xa);
|
||||
}
|
||||
|
||||
}
|
||||
/* Create text buffer for transfer to backend */
|
||||
if ((cbx = cbuf_new()) == NULL)
|
||||
goto done;
|
||||
/* For internal XML protocol: add username attribute for access control
|
||||
*/
|
||||
username = clicon_username_get(h);
|
||||
again:
|
||||
cbuf_reset(cbx);
|
||||
cprintf(cbx, "<rpc username=\"%s\" xmlns:%s=\"%s\">",
|
||||
username?username:"",
|
||||
NETCONF_BASE_PREFIX,
|
||||
|
|
@ -557,30 +625,9 @@ api_data_put(clicon_handle h,
|
|||
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0)
|
||||
goto done;
|
||||
if ((xe = xpath_first(xret, "//rpc-error")) != NULL){
|
||||
/* If the error is not data-exists, then return error now
|
||||
* OR we have run again with replace
|
||||
*/
|
||||
if (xpath_first(xe, ".[error-tag=\"data-exists\"]") == NULL ||
|
||||
op == OP_REPLACE){
|
||||
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
/* If it is data-exists, then set operator to replace and try again */
|
||||
if (xret){
|
||||
xml_free(xret);
|
||||
xret = NULL;
|
||||
}
|
||||
if ((xa = xml_find_type(xdata, NULL, "operation", CX_ATTR)) == NULL){
|
||||
clicon_err(OE_XML, ENOENT, "operation attr not found (shouldnt happen)");
|
||||
goto done;
|
||||
}
|
||||
op = OP_REPLACE;
|
||||
if (xml_value_set(xa, xml_operation2str(op)) < 0)
|
||||
goto done;
|
||||
cbuf_reset(cbx);
|
||||
clicon_debug(1, "%s Failed with create, trying replace",__FUNCTION__);
|
||||
goto again;
|
||||
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
cbuf_reset(cbx);
|
||||
/* commit/discard should be done automaticaly by the system, therefore
|
||||
|
|
@ -640,6 +687,8 @@ api_data_put(clicon_handle h,
|
|||
retval = 0;
|
||||
done:
|
||||
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
||||
if (cbpath)
|
||||
cbuf_reset(cbpath);
|
||||
if (xret)
|
||||
xml_free(xret);
|
||||
if (xerr)
|
||||
|
|
@ -657,6 +706,103 @@ api_data_put(clicon_handle h,
|
|||
return retval;
|
||||
} /* api_data_put */
|
||||
|
||||
|
||||
/*! Generic REST PUT method
|
||||
* @param[in] h CLIXON handle
|
||||
* @param[in] r Fastcgi request handle
|
||||
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
|
||||
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
|
||||
* @param[in] pi Offset, where to start pcvec
|
||||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||
* @param[in] data Stream input data
|
||||
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||
* @param[in] media_out Output media
|
||||
|
||||
* @note restconf PUT is mapped to edit-config replace.
|
||||
* @see RFC8040 Sec 4.5 PUT
|
||||
* @see api_data_post
|
||||
* @example
|
||||
curl -X PUT -d '{"enabled":"false"}' http://127.0.0.1/restconf/data/interfaces/interface=eth1
|
||||
*
|
||||
PUT:
|
||||
A request message-body MUST be present, representing the new data resource, or the server
|
||||
MUST return a "400 Bad Request" status-line.
|
||||
|
||||
...if the PUT request creates a new resource, a "201 Created" status-line is returned.
|
||||
If an existing resource is modified, a "204 No Content" status-line is returned.
|
||||
|
||||
* Netconf: <edit-config> (nc:operation="create/replace")
|
||||
* Note RFC8040 says that if an object is created, 201 is returned, if replaced 204
|
||||
* is returned. But the restconf client does not know if it is replaced or created,
|
||||
* only the server knows that. Solutions:
|
||||
* 1) extend the netconf <ok/> so it returns if created/replaced. But that would lead
|
||||
* to extension of netconf that may hit other places.
|
||||
* 2) Send a get first and see if the resource exists, and then send replace/create.
|
||||
* Will always produce an extra message and the GET may potetnially waste bw.
|
||||
* 3) Try to create first, if that fails (with conflict) then try replace.
|
||||
* --> Best solution and applied here
|
||||
*/
|
||||
int
|
||||
api_data_put(clicon_handle h,
|
||||
FCGX_Request *r,
|
||||
char *api_path0,
|
||||
cvec *pcvec,
|
||||
int pi,
|
||||
cvec *qvec,
|
||||
char *data,
|
||||
int pretty,
|
||||
restconf_media media_out)
|
||||
{
|
||||
restconf_media media_in;
|
||||
|
||||
media_in = restconf_content_type(r);
|
||||
return api_data_write(h, r, api_path0, pcvec, pi, qvec, data, pretty,
|
||||
media_in, media_out, 0);
|
||||
}
|
||||
|
||||
/*! Generic REST PATCH method for plain patch
|
||||
* @param[in] h CLIXON handle
|
||||
* @param[in] r Fastcgi request handle
|
||||
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
|
||||
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
|
||||
* @param[in] pi Offset, where to start pcvec
|
||||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||
* @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")
|
||||
* See RFC8040 Sec 4.6.1
|
||||
* Plain patch can be used to create or update, but not delete, a child
|
||||
* resource within the target resource.
|
||||
* NOTE: If the target resource instance does not exist, the server MUST NOT
|
||||
* create it. (CANT BE DONE WITH NETCONF)
|
||||
*/
|
||||
int
|
||||
api_data_patch(clicon_handle h,
|
||||
FCGX_Request *r,
|
||||
char *api_path0,
|
||||
cvec *pcvec,
|
||||
int pi,
|
||||
cvec *qvec,
|
||||
char *data,
|
||||
int pretty,
|
||||
restconf_media media_out)
|
||||
{
|
||||
restconf_media media_in;
|
||||
int ret;
|
||||
|
||||
media_in = restconf_content_type(r);
|
||||
if (media_in == YANG_DATA_XML || media_in == YANG_DATA_JSON){
|
||||
/* plain patch */
|
||||
ret = api_data_write(h, r, api_path0, pcvec, pi, qvec, data, pretty,
|
||||
media_in, media_out, 1);
|
||||
}
|
||||
else{ /* Other patches are NYI */
|
||||
ret = restconf_notimplemented(r);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*! Generic REST DELETE method translated to edit-config
|
||||
* @param[in] h CLIXON handle
|
||||
* @param[in] r Fastcgi request handle
|
||||
|
|
|
|||
|
|
@ -45,8 +45,13 @@ int api_data_options(clicon_handle h, FCGX_Request *r);
|
|||
int api_data_put(clicon_handle h, FCGX_Request *r, char *api_path,
|
||||
cvec *pcvec, int pi,
|
||||
cvec *qvec, char *data,
|
||||
int pretty,
|
||||
restconf_media media_in, restconf_media media_out);
|
||||
int pretty, restconf_media media_out);
|
||||
|
||||
int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path,
|
||||
cvec *pcvec, int pi,
|
||||
cvec *qvec, char *data, int pretty,
|
||||
restconf_media media_out);
|
||||
|
||||
int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi,
|
||||
int pretty, restconf_media media_out);
|
||||
|
||||
|
|
|
|||
|
|
@ -116,7 +116,10 @@ api_data_get2(clicon_handle h,
|
|||
cvec *nsc = NULL;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
yspec = clicon_dbspec_yang(h);
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "No DB_SPEC");
|
||||
goto done;
|
||||
}
|
||||
if ((cbpath = cbuf_new()) == NULL)
|
||||
goto done;
|
||||
cprintf(cbpath, "/");
|
||||
|
|
|
|||
|
|
@ -1,249 +0,0 @@
|
|||
/*
|
||||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
the GNU General Public License Version 3 or later (the "GPL"),
|
||||
in which case the provisions of the GPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of the GPL, and not to allow others to
|
||||
use your version of this file under the terms of Apache License version 2,
|
||||
indicate your decision by deleting the provisions above and replace them with
|
||||
the notice and other provisions required by the GPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the Apache License version 2 or the GPL.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
* See rfc8040
|
||||
|
||||
* sudo apt-get install libfcgi-dev
|
||||
* gcc -o fastcgi fastcgi.c -lfcgi
|
||||
|
||||
* sudo su -c "/www-data/clixon_restconf -D 1 f /usr/local/etc/example.xml " -s /bin/sh www-data
|
||||
|
||||
* This is the interface:
|
||||
* api/data/profile=<name>/metric=<name> PUT data:enable=<flag>
|
||||
* api/test
|
||||
+----------------------------+--------------------------------------+
|
||||
| 100 Continue | POST accepted, 201 should follow |
|
||||
| 200 OK | Success with response message-body |
|
||||
| 201 Created | POST to create a resource success |
|
||||
| 204 No Content | Success without response message- |
|
||||
| | body |
|
||||
| 304 Not Modified | Conditional operation not done |
|
||||
| 400 Bad Request | Invalid request message |
|
||||
| 401 Unauthorized | Client cannot be authenticated |
|
||||
| 403 Forbidden | Access to resource denied |
|
||||
| 404 Not Found | Resource target or resource node not |
|
||||
| | found |
|
||||
| 405 Method Not Allowed | Method not allowed for target |
|
||||
| | resource |
|
||||
| 409 Conflict | Resource or lock in use |
|
||||
| 412 Precondition Failed | Conditional method is false |
|
||||
| 413 Request Entity Too | too-big error |
|
||||
| Large | |
|
||||
| 414 Request-URI Too Large | too-big error |
|
||||
| 415 Unsupported Media Type | non RESTCONF media type |
|
||||
| 500 Internal Server Error | operation-failed |
|
||||
| 501 Not Implemented | unknown-operation |
|
||||
| 503 Service Unavailable | Recoverable server error |
|
||||
+----------------------------+--------------------------------------+
|
||||
Mapping netconf error-tag -> status code
|
||||
+-------------------------+-------------+
|
||||
| <error‑tag> | status code |
|
||||
+-------------------------+-------------+
|
||||
| in-use | 409 |
|
||||
| invalid-value | 400 |
|
||||
| too-big | 413 |
|
||||
| missing-attribute | 400 |
|
||||
| bad-attribute | 400 |
|
||||
| unknown-attribute | 400 |
|
||||
| bad-element | 400 |
|
||||
| unknown-element | 400 |
|
||||
| unknown-namespace | 400 |
|
||||
| access-denied | 403 |
|
||||
| lock-denied | 409 |
|
||||
| resource-denied | 409 |
|
||||
| rollback-failed | 500 |
|
||||
| data-exists | 409 |
|
||||
| data-missing | 409 |
|
||||
| operation-not-supported | 501 |
|
||||
| operation-failed | 500 |
|
||||
| partial-operation | 500 |
|
||||
| malformed-message | 400 |
|
||||
+-------------------------+-------------+
|
||||
|
||||
* "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clixon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <syslog.h>
|
||||
#include <fcntl.h>
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
#include <signal.h>
|
||||
#include <limits.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clixon/clixon.h>
|
||||
|
||||
#include <fcgiapp.h> /* Need to be after clixon_xml-h due to attribute format */
|
||||
|
||||
#include "restconf_lib.h"
|
||||
#include "restconf_methods_patch.h"
|
||||
|
||||
|
||||
/*! Generic REST PATCH method for plain patch
|
||||
* @param[in] h CLIXON handle
|
||||
* @param[in] r Fastcgi request handle
|
||||
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
|
||||
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
|
||||
* @param[in] pi Offset, where to start pcvec
|
||||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||
* @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")
|
||||
* See RFC8040 Sec 4.6.1
|
||||
*/
|
||||
int
|
||||
api_data_patch(clicon_handle h,
|
||||
FCGX_Request *r,
|
||||
char *api_path0,
|
||||
cvec *pcvec,
|
||||
int pi,
|
||||
cvec *qvec,
|
||||
char *data,
|
||||
int pretty,
|
||||
restconf_media media_in,
|
||||
restconf_media media_out)
|
||||
{
|
||||
int retval = -1;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
the GNU General Public License Version 3 or later (the "GPL"),
|
||||
in which case the provisions of the GPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of the GPL, and not to allow others to
|
||||
use your version of this file under the terms of Apache License version 2,
|
||||
indicate your decision by deleting the provisions above and replace them with
|
||||
the notice and other provisions required by the GPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the Apache License version 2 or the GPL.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
|
||||
* Restconf method implementation
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _RESTCONF_METHODS_PATCH_H_
|
||||
#define _RESTCONF_METHODS_PATCH_H_
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path,
|
||||
cvec *pcvec, int pi,
|
||||
cvec *qvec, char *data, int pretty,
|
||||
restconf_media media_in, restconf_media media_out);
|
||||
|
||||
#endif /* _RESTCONF_METHODS_PATCH_H_ */
|
||||
|
|
@ -73,7 +73,6 @@
|
|||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||
* @param[in] data Stream input data
|
||||
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||
* @param[in] media_in Input media
|
||||
* @param[in] media_out Output media
|
||||
* restconf POST is mapped to edit-config create.
|
||||
* @see RFC8040 Sec 4.4.1
|
||||
|
|
@ -106,7 +105,6 @@ api_data_post(clicon_handle h,
|
|||
cvec *qvec,
|
||||
char *data,
|
||||
int pretty,
|
||||
restconf_media media_in,
|
||||
restconf_media media_out)
|
||||
{
|
||||
int retval = -1;
|
||||
|
|
@ -130,6 +128,7 @@ api_data_post(clicon_handle h,
|
|||
char *username;
|
||||
int nullspec = 0;
|
||||
int ret;
|
||||
restconf_media media_in;
|
||||
|
||||
clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path);
|
||||
clicon_debug(1, "%s data:\"%s\"", __FUNCTION__, data);
|
||||
|
|
@ -148,7 +147,7 @@ api_data_post(clicon_handle h,
|
|||
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &ybot)) < 0)
|
||||
goto done;
|
||||
if (ybot)
|
||||
ymodapi=ys_module(ybot);
|
||||
ymodapi = ys_module(ybot);
|
||||
if (ret == 0){ /* validation failed */
|
||||
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
||||
goto done;
|
||||
|
|
@ -171,7 +170,22 @@ api_data_post(clicon_handle h,
|
|||
cbuf_free(ccc);
|
||||
}
|
||||
#endif
|
||||
/* 4.4.1: The message-body MUST contain exactly one instance of the
|
||||
* expected data resource. (tested again below)
|
||||
*/
|
||||
if (data == NULL || strlen(data) == 0){
|
||||
if (netconf_malformed_message_xml(&xerr, "The message-body MUST contain exactly one instance of the expected data resource") < 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;
|
||||
}
|
||||
/* Parse input data as json or xml into xml */
|
||||
media_in = restconf_content_type(r);
|
||||
switch (media_in){
|
||||
case YANG_DATA_XML:
|
||||
if (xml_parse_string(data, NULL, &xdata0) < 0){
|
||||
|
|
@ -217,12 +231,16 @@ api_data_post(clicon_handle h,
|
|||
goto ok;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
restconf_unsupported_media(r);
|
||||
goto ok;
|
||||
break;
|
||||
} /* switch media_in */
|
||||
/* 4.4.1: The message-body MUST contain exactly one instance of the
|
||||
* expected data resource.
|
||||
*/
|
||||
if (xml_child_nr(xdata0) != 1){
|
||||
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
||||
if (netconf_malformed_message_xml(&xerr, "The message-body MUST contain exactly one instance of the expected data resource") < 0)
|
||||
goto done;
|
||||
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
|
||||
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
|
||||
|
|
@ -401,7 +419,6 @@ api_data_post(clicon_handle h,
|
|||
* @param[in] yrpc Yang rpc spec
|
||||
* @param[in] xrpc XML pointer to rpc method
|
||||
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||
* @param[in] media_in Input media
|
||||
* @param[in] media_out Output media
|
||||
* @retval 1 OK
|
||||
* @retval 0 Fail, Error message sent
|
||||
|
|
@ -423,7 +440,6 @@ api_operations_post_input(clicon_handle h,
|
|||
yang_stmt *yrpc,
|
||||
cxobj *xrpc,
|
||||
int pretty,
|
||||
restconf_media media_in,
|
||||
restconf_media media_out)
|
||||
{
|
||||
int retval = -1;
|
||||
|
|
@ -434,6 +450,7 @@ api_operations_post_input(clicon_handle h,
|
|||
cxobj *x;
|
||||
cbuf *cbret = NULL;
|
||||
int ret;
|
||||
restconf_media media_in;
|
||||
|
||||
clicon_debug(1, "%s %s", __FUNCTION__, data);
|
||||
if ((cbret = cbuf_new()) == NULL){
|
||||
|
|
@ -441,6 +458,7 @@ api_operations_post_input(clicon_handle h,
|
|||
goto done;
|
||||
}
|
||||
/* Parse input data as json or xml into xml */
|
||||
media_in = restconf_content_type(r);
|
||||
switch (media_in){
|
||||
case YANG_DATA_XML:
|
||||
if (xml_parse_string(data, yspec, &xdata) < 0){
|
||||
|
|
@ -477,6 +495,10 @@ api_operations_post_input(clicon_handle h,
|
|||
goto fail;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
restconf_unsupported_media(r);
|
||||
goto fail;
|
||||
break;
|
||||
} /* switch media_in */
|
||||
xml_name_set(xdata, "data");
|
||||
/* Here xdata is:
|
||||
|
|
@ -676,7 +698,6 @@ api_operations_post_output(clicon_handle h,
|
|||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||
* @param[in] data Stream input data
|
||||
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||
* @param[in] media_in Input media media
|
||||
* @param[in] media_out Output media
|
||||
* See RFC 8040 Sec 3.6 / 4.4.2
|
||||
* @note We map post to edit-config create.
|
||||
|
|
@ -708,7 +729,6 @@ api_operations_post(clicon_handle h,
|
|||
cvec *qvec,
|
||||
char *data,
|
||||
int pretty,
|
||||
restconf_media media_in,
|
||||
restconf_media media_out)
|
||||
{
|
||||
int retval = -1;
|
||||
|
|
@ -825,7 +845,7 @@ api_operations_post(clicon_handle h,
|
|||
clicon_debug(1, "%s : 4. Parse input data: %s", __FUNCTION__, data);
|
||||
if (data && strlen(data)){
|
||||
if ((ret = api_operations_post_input(h, r, data, yspec, yrpc, xbot,
|
||||
pretty, media_in, media_out)) < 0)
|
||||
pretty, media_out)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto ok;
|
||||
|
|
|
|||
|
|
@ -45,15 +45,12 @@ int api_data_post(clicon_handle h, FCGX_Request *r, char *api_path,
|
|||
cvec *pcvec, int pi,
|
||||
cvec *qvec, char *data,
|
||||
int pretty,
|
||||
restconf_media media_in,
|
||||
restconf_media media_out);
|
||||
|
||||
int api_operations_post(clicon_handle h, FCGX_Request *r,
|
||||
char *path,
|
||||
cvec *pcvec, int pi, cvec *qvec, char *data,
|
||||
int pretty,
|
||||
restconf_media media_in,
|
||||
restconf_media media_out);
|
||||
int pretty, restconf_media media_out);
|
||||
|
||||
|
||||
#endif /* _RESTCONF_METHODS_POST_H_ */
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#!/bin/bash
|
||||
# Jukebox example from rfc 8040 Appendix A.1
|
||||
# ASsumes fjukebox is set to name of yang file
|
||||
# Assumes fjukebox is set to name of yang file
|
||||
# An extra leaf-list is added (clixon)
|
||||
|
||||
cat <<EOF > $fjukebox
|
||||
module example-jukebox {
|
||||
|
|
|
|||
|
|
@ -202,8 +202,8 @@ expectpart "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '
|
|||
new "Add leaf description using POST"
|
||||
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"
|
||||
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:'
|
||||
new "Add nothing using POST (expect fail)"
|
||||
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"The message-body MUST contain exactly one instance of the expected data resource"}}}'
|
||||
|
||||
new "restconf Check description added"
|
||||
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","description":"The-first-interface","type":"clixon-example:eth","enabled":true,"oper-status":"up"}]}}
|
||||
|
|
|
|||
|
|
@ -9,13 +9,26 @@ APPNAME=example
|
|||
cfg=$dir/conf.xml
|
||||
fjukebox=$dir/example-jukebox.yang
|
||||
|
||||
# A "system" module as defined in B.2.4
|
||||
cat <<EOF > $dir/example-system.yang
|
||||
module example-system {
|
||||
namespace "http://example.com/ns/example-system";
|
||||
prefix "ex";
|
||||
container system {
|
||||
leaf enable-jukebox-streaming {
|
||||
type boolean;
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# <CLICON_YANG_MODULE_MAIN>example</CLICON_YANG_MODULE_MAIN>
|
||||
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_YANG_MAIN_DIR>$dir</CLICON_YANG_MAIN_DIR>
|
||||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
|
||||
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
|
||||
|
|
@ -52,102 +65,120 @@ wait_backend
|
|||
wait_restconf
|
||||
|
||||
new "B.1.1. Retrieve the Top-Level API Resource root"
|
||||
expectpart "$(curl -s -i -X GET -H 'Accept: application/xrd+xml' http://localhost/.well-known/host-meta)" 0 "HTTP/1.1 200 OK" "Content-Type: application/xrd+xml" "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>"
|
||||
expectpart "$(curl -si -X GET -H 'Accept: application/xrd+xml' http://localhost/.well-known/host-meta)" 0 "HTTP/1.1 200 OK" "Content-Type: application/xrd+xml" "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>"
|
||||
|
||||
d='{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2016-06-21"}}'
|
||||
new "B.1.1. Retrieve the Top-Level API Resource /restconf json"
|
||||
expectpart "$(curl -s -i -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" "$d"
|
||||
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" "$d"
|
||||
|
||||
new "B.1.1. Retrieve the Top-Level API Resource /restconf xml (not in RFC)"
|
||||
expectpart "$(curl -s -i -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+xml" '<restconf xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><data/><operations/><yang-library-version>2016-06-21</yang-library-version></restconf>'
|
||||
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+xml" '<restconf xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><data/><operations/><yang-library-version>2016-06-21</yang-library-version></restconf>'
|
||||
|
||||
# This just catches the header and the jukebox module, the RFC has foo and bar which
|
||||
# seems wrong to recreate
|
||||
new "B.1.2. Retrieve the Server Module Information"
|
||||
expectpart "$(curl -s -i -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/ietf-yang-library:modules-state)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" '{"ietf-yang-library:modules-state":{"module-set-id":' '"module":\[{"name":"example-jukebox","revision":"2016-08-15","namespace":"http://example.com/ns/example-jukebox","conformance-type":"implement"}'
|
||||
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/ietf-yang-library:modules-state)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" '{"ietf-yang-library:modules-state":{"module-set-id":' '"module":\[{"name":"example-jukebox","revision":"2016-08-15","namespace":"http://example.com/ns/example-jukebox","conformance-type":"implement"}'
|
||||
|
||||
new "B.1.3. Retrieve the Server Capability Information"
|
||||
expectpart "$(curl -s -i -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/capabilities)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+xml" 'Cache-Control: no-cache' '<capabilities xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf-monitoring"><capability>urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit</capability></capabilities>'
|
||||
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/capabilities)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+xml" 'Cache-Control: no-cache' '<capabilities xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf-monitoring"><capability>urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit</capability></capabilities>'
|
||||
|
||||
new "B.2.1. Create New Data Resources (artist+json)"
|
||||
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library -d '{"example-jukebox:artist":[{"name":"Foo Fighters"}]}')" 0 "HTTP/1.1 201 Created" "Location: http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters"
|
||||
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library -d '{"example-jukebox:artist":[{"name":"Foo Fighters"}]}')" 0 "HTTP/1.1 201 Created" "Location: http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters"
|
||||
|
||||
new "B.2.1. Create New Data Resources (album+xml)"
|
||||
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d '<album xmlns="http://example.com/ns/example-jukebox"><name>Wasting Light</name><year>2011</year></album>')" 0 "HTTP/1.1 201 Created" "Location: http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light"
|
||||
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d '<album xmlns="http://example.com/ns/example-jukebox"><name>Wasting Light</name><year>2011</year></album>')" 0 "HTTP/1.1 201 Created" "Location: http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light"
|
||||
|
||||
new "B.2.1. Add Data Resources again (conflict - not in RFC)"
|
||||
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d '<album xmlns="http://example.com/ns/example-jukebox"><name>Wasting Light</name><year>2011</year></album>')" 0 "HTTP/1.1 409 Conflict"
|
||||
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d '<album xmlns="http://example.com/ns/example-jukebox"><name>Wasting Light</name><year>2011</year></album>')" 0 "HTTP/1.1 409 Conflict"
|
||||
|
||||
new "4.5. PUT replace content"
|
||||
expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '{"example-jukebox:album":[{"name":"Wasting Light","genre":"example-jukebox:alternative","year":2011}]}')" 0 "HTTP/1.1 204 No Content"
|
||||
|
||||
new "B.2.2. Added genre"
|
||||
# Here the jbox identity is added
|
||||
expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '{"example-jukebox:album":[{"name":"Wasting Light","genre":"example-jukebox:alternative","year":2011}]}')" 0 "HTTP/1.1 204 No Content"
|
||||
|
||||
new "4.5. PUT replace content (xml encoding)"
|
||||
expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '<album xmlns="http://example.com/ns/example-jukebox" xmlns:jbox="http://example.com/ns/example-jukebox"><name>Wasting Light</name><genre>jbox:alternative</genre><year>2011</year></album>')" 0 "HTTP/1.1 204 No Content"
|
||||
expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '<album xmlns="http://example.com/ns/example-jukebox" xmlns:jbox="http://example.com/ns/example-jukebox"><name>Wasting Light</name><genre>jbox:alternative</genre><year>2011</year></album>')" 0 "HTTP/1.1 204 No Content"
|
||||
|
||||
new "4.5. PUT create new identity"
|
||||
expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":[{"name":"London Calling","year":1979}]}')" 0 "HTTP/1.1 201 Created"
|
||||
expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":[{"name":"London Calling","year":1979}]}')" 0 "HTTP/1.1 201 Created"
|
||||
|
||||
new "restconf DELETE whole datastore"
|
||||
expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 ""
|
||||
new "4.5. Check jukebox content: 1 Clash and 1 Foo fighters album"
|
||||
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Clash</name><album><name>London Calling</name><year>1979</year></album></artist><artist><name>Foo Fighters</name><album xmlns:jbox="http://example.com/ns/example-jukebox"><name>Wasting Light</name><genre>jbox:alternative</genre><year>2011</year></album></artist></library></jukebox>'
|
||||
|
||||
# First use of PATCH
|
||||
new "B.2.2. Detect Datastore Resource Entity-Tag Change (XXX if-unmodified)"
|
||||
expectpart "$(curl -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light/genre -d '{"example-jukebox:genre":"example-jukebox:alternative"}')" 0 'HTTP/1.1 204 No Content'
|
||||
|
||||
new "B.2.3. Edit a Datastore Resource (Add 1 Foo fighter and Nick cave album)"
|
||||
expectpart "$(curl -si -X PATCH -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data -d '<data xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><system xmlns="http://example.com/ns/example-system"><enable-jukebox-streaming>true</enable-jukebox-streaming></system><jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Foo Fighters</name><album><name>One by One</name><year>2012</year></album></artist><artist><name>Nick Cave and the Bad Seeds</name><album><name>Tender Prey</name><year>1988</year></album></artist></library></jukebox></data>')" 0 'HTTP/1.1 204 No Content'
|
||||
|
||||
new "B.2.3. Check patch system"
|
||||
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-system:system -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<system xmlns="http://example.com/ns/example-system"><enable-jukebox-streaming>true</enable-jukebox-streaming></system>'
|
||||
|
||||
new "B.2.3. Check jukebox: 1 Clash, 2 Foo Fighters, 1 Nick Cave"
|
||||
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Clash</name><album><name>London Calling</name><year>1979</year></album></artist><artist><name>Foo Fighters</name><album><name>One by One</name><year>2012</year></album><album xmlns:jbox="http://example.com/ns/example-jukebox"><name>Wasting Light</name><genre>alternative</genre><year>2011</year></album></artist><artist><name>Nick Cave and the Bad Seeds</name><album><name>Tender Prey</name><year>1988</year></album></artist></library></jukebox>'
|
||||
|
||||
new "B.2.4. Replace a Datastore Resource"
|
||||
expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data -d '<data xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Foo Fighters</name><album><name>One by One</name><year>2012</year></album></artist><artist><name>Nick Cave and the Bad Seeds</name><album><name>Tender Prey</name><year>1988</year></album></artist></library></jukebox></data>')" 0 "HTTP/1.1 201 Created"
|
||||
expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data -d '<data xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Foo Fighters</name><album><name>One by One</name><year>2012</year></album></artist><artist><name>Nick Cave and the Bad Seeds</name><album><name>Tender Prey</name><year>1988</year></album></artist></library></jukebox></data>')" 0 "HTTP/1.1 204 No Content"
|
||||
|
||||
new "B.2.4. Check replace"
|
||||
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Foo Fighters</name><album><name>One by One</name><year>2012</year></album></artist><artist><name>Nick Cave and the Bad Seeds</name><album><name>Tender Prey</name><year>1988</year></album></artist></library></jukebox>'
|
||||
|
||||
new "restconf DELETE whole datastore"
|
||||
expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 ""
|
||||
|
||||
new 'B.3.4. "insert" Parameter'
|
||||
JSON="{\"example-jukebox:song\":[{\"index\":1,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Rope']\"}]}"
|
||||
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=1'
|
||||
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=1'
|
||||
|
||||
new 'B.3.4. "insert" Parameter first (RFC example says after)'
|
||||
JSON="{\"example-jukebox:song\":[{\"index\":0,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Bridge Burning']\"}]}"
|
||||
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=0'
|
||||
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=0'
|
||||
|
||||
new 'B.3.4. "insert" Parameter check order'
|
||||
RES="<playlist xmlns=\"http://example.com/ns/example-jukebox\"><name>Foo-One</name><song><index>0</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>1</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]</id></song></playlist>"
|
||||
expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
|
||||
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
|
||||
|
||||
new 'B.3.5. "point" Parameter (before for more interesting order: 0,2,1)'
|
||||
JSON="{\"example-jukebox:song\":[{\"index\":2,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Bridge Burning']\"}]}"
|
||||
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' -d "$JSON" http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=before\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D1 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=2'
|
||||
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' -d "$JSON" http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=before\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D1 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=2'
|
||||
|
||||
new 'B.3.5. "point" check order (0,2,1)'
|
||||
RES="<playlist xmlns=\"http://example.com/ns/example-jukebox\"><name>Foo-One</name><song><index>0</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>2</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>1</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]</id></song></playlist>"
|
||||
expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
|
||||
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
|
||||
|
||||
new 'B.3.5. "point" Parameter 3 after 2 (using PUT)'
|
||||
JSON="{\"example-jukebox:song\":[{\"index\":3,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Something else']\"}]}"
|
||||
expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+json' -d "$JSON" http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=3?insert=after\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D2 )" 0 "HTTP/1.1 201 Created"
|
||||
expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' -d "$JSON" http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=3?insert=after\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D2 )" 0 "HTTP/1.1 201 Created"
|
||||
|
||||
new 'B.3.5. "point" check order (0,2,3,1)'
|
||||
RES="<playlist xmlns=\"http://example.com/ns/example-jukebox\"><name>Foo-One</name><song><index>0</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>2</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>3</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Something else'\]</id></song><song><index>1</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]</id></song></playlist>"
|
||||
expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
|
||||
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
|
||||
|
||||
new "restconf DELETE whole datastore"
|
||||
expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 ""
|
||||
|
||||
new 'B.3.4. "insert/point" leaf-list 3 (not in RFC)'
|
||||
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"3"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=3'
|
||||
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"3"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=3'
|
||||
|
||||
new 'B.3.4. "insert/point" leaf-list 2 first'
|
||||
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=first -d '{"example-jukebox:extra":"2"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=2'
|
||||
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=first -d '{"example-jukebox:extra":"2"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=2'
|
||||
|
||||
new 'B.3.4. "insert/point" leaf-list 1 last'
|
||||
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=1'
|
||||
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=1'
|
||||
|
||||
#new 'B.3.4. "insert/point" move leaf-list 1 last'
|
||||
#- restconf cannot move a leaf-list(list?) item
|
||||
#expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=1'
|
||||
#expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=1'
|
||||
|
||||
new 'B.3.5. "insert/point" leaf-list check order (2,3,1)'
|
||||
expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<extra xmlns="http://example.com/ns/example-jukebox">2</extra><extra xmlns="http://example.com/ns/example-jukebox">3</extra><extra xmlns="http://example.com/ns/example-jukebox">1</extra>'
|
||||
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<extra xmlns="http://example.com/ns/example-jukebox">2</extra><extra xmlns="http://example.com/ns/example-jukebox">3</extra><extra xmlns="http://example.com/ns/example-jukebox">1</extra>'
|
||||
|
||||
new 'B.3.5. "point" Parameter leaf-list 4 before 3'
|
||||
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' -d '{"example-jukebox:extra":"4"}' http://localhost/restconf/data?insert=before\&point=%2Fexample-jukebox%3Aextra%3D3 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=4'
|
||||
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' -d '{"example-jukebox:extra":"4"}' http://localhost/restconf/data?insert=before\&point=%2Fexample-jukebox%3Aextra%3D3 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=4'
|
||||
|
||||
new 'B.3.5. "insert/point" leaf-list check order (2,4,3,1)'
|
||||
expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<extra xmlns="http://example.com/ns/example-jukebox">2</extra><extra xmlns="http://example.com/ns/example-jukebox">4</extra><extra xmlns="http://example.com/ns/example-jukebox">3</extra><extra xmlns="http://example.com/ns/example-jukebox">1</extra>'
|
||||
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<extra xmlns="http://example.com/ns/example-jukebox">2</extra><extra xmlns="http://example.com/ns/example-jukebox">4</extra><extra xmlns="http://example.com/ns/example-jukebox">3</extra><extra xmlns="http://example.com/ns/example-jukebox">1</extra>'
|
||||
|
||||
if false; then # NYI
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue