- Moved yang patch code to new files restconf_methods_patch.[ch]

- Started modifying patch code to style guidelines
- Made patch test independent of example
- Added developers style guidelines
This commit is contained in:
Olof hagsand 2021-08-15 20:38:45 +02:00
parent 629757d1fd
commit 8db716ca07
15 changed files with 954 additions and 2098 deletions

View file

@ -35,7 +35,7 @@ Expected: September, 2021
### New features ### New features
* Restconf YANG PATCH according to RFC 8072 * Restconf YANG PATCH according to RFC 8072 (Work in progress)
* Experimental: enable by setting YANG_PATCH in include/clixon_custom.h * Experimental: enable by setting YANG_PATCH in include/clixon_custom.h
* Thanks to Alan Yaniger for providing this patch * Thanks to Alan Yaniger for providing this patch

View file

@ -97,6 +97,7 @@ APPSRC += restconf_err.c
APPSRC += restconf_methods.c APPSRC += restconf_methods.c
APPSRC += restconf_methods_post.c APPSRC += restconf_methods_post.c
APPSRC += restconf_methods_get.c APPSRC += restconf_methods_get.c
APPSRC += restconf_methods_patch.c
APPSRC += restconf_root.c APPSRC += restconf_root.c
APPSRC += restconf_main_$(with_restconf).c APPSRC += restconf_main_$(with_restconf).c
ifeq ($(with_restconf),native) ifeq ($(with_restconf),native)

View file

@ -265,6 +265,7 @@ api_return_err(clicon_handle h,
goto done; goto done;
switch (media){ switch (media){
case YANG_DATA_XML: case YANG_DATA_XML:
case YANG_PATCH_XML:
clicon_debug(1, "%s code:%d", __FUNCTION__, code); clicon_debug(1, "%s code:%d", __FUNCTION__, code);
if (pretty){ if (pretty){
cprintf(cb, " <errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">\n"); cprintf(cb, " <errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">\n");
@ -280,6 +281,7 @@ api_return_err(clicon_handle h,
} }
break; break;
case YANG_DATA_JSON: case YANG_DATA_JSON:
case YANG_PATCH_JSON:
clicon_debug(1, "%s code:%d", __FUNCTION__, code); clicon_debug(1, "%s code:%d", __FUNCTION__, code);
if (pretty){ if (pretty){
cprintf(cb, "{\n\"ietf-restconf:errors\" : "); cprintf(cb, "{\n\"ietf-restconf:errors\" : ");

View file

@ -65,8 +65,6 @@
/* cligen */ /* cligen */
#include <cligen/cligen.h> #include <cligen/cligen.h>
// TODO - remove this include if cbuf_trunc() is added to cligen repo
#include "../cligen/cligen_buf_internal.h"
/* clicon */ /* clicon */
#include <clixon/clixon.h> #include <clixon/clixon.h>
@ -75,8 +73,9 @@
#include "restconf_handle.h" #include "restconf_handle.h"
#include "restconf_api.h" #include "restconf_api.h"
#include "restconf_err.h" #include "restconf_err.h"
#include "restconf_methods.h"
#include "restconf_methods_post.h" #include "restconf_methods_post.h"
#include "restconf_methods_patch.h"
#include "restconf_methods.h"
/*! REST OPTIONS method /*! REST OPTIONS method
* According to restconf * According to restconf
@ -188,7 +187,7 @@ match_list_keys(yang_stmt *y,
* @param[in] media_in Restconf input media * @param[in] media_in Restconf input media
* @param[in] media_out Restconf output media * @param[in] media_out Restconf output media
*/ */
static int int
api_data_write(clicon_handle h, api_data_write(clicon_handle h,
void *req, void *req,
char *api_path0, char *api_path0,
@ -582,710 +581,6 @@ api_data_write(clicon_handle h,
return retval; return retval;
} /* api_data_write */ } /* api_data_write */
#ifdef YANG_PATCH
/*! Free memory after a NULL pointer check
*
* @param [in] str void pointer to memory to be freed
*
*/
static void yang_patch_free_mem(void *p)
{
if (p != NULL)
free(p);
}
/*! Return a value within XML tags
* @param [in] nsc namespace context
* @param [in] xn cxobj containing XML with the current edit
* @param [in] val cbuf to which the value will be written
* @param [in] key string containing the tag
* @retval 0 success
* @retval <0 failure
*/
static int yang_patch_get_xval(
cvec* nsc,
cxobj* xn,
cbuf* val,
const char* key
)
{
cxobj **vec = NULL;
size_t veclen = 0;
char* tmp_val = NULL;
int ret = xpath_vec(xn, nsc, "%s", &vec, &veclen, key);
if (ret < 0) {
return ret;
}
cxobj *xn_tmp = NULL;
if (veclen == 1) { //veclen should always be 1
xn_tmp = vec[0];
}
if (xn_tmp != NULL) {
tmp_val = xml_body(xn_tmp);
cbuf_append_str(val, tmp_val);
}
return 0;
}
// TODO - add this to cligen repo if it is approved
/*! Truncate a cbuf
*
* @param [in] cb cligen buffer allocated by cbuf_new(), may be reallocated.
* @param [in] int pos position at which to truncate
* @retval new cbuf containing the truncated string (old buffer remains as it was)
* @retval NULL Error
*/
cbuf*
cbuf_trunc(cbuf *cb,
int pos)
{
if (pos < 0 || pos > cb->cb_strlen){
errno = EINVAL;
return NULL;
}
/* Ensure buffer is right size */
cbuf* new_buf = cbuf_new_alloc(pos + 1);
if (new_buf == NULL)
return NULL;
strncpy(new_buf->cb_buffer, cb->cb_buffer, pos);
new_buf->cb_strlen = pos;
return new_buf;
}
/*! Add square brackets after the surrounding curly brackets in JSON
* Needed, in order to modify the result of xml2json_cbuf() to be valid input
* to api_dta_post() and api_dta_write()
* @param [in] x_simple_patch a cxobj to pass to xml2json_cbuf()
* @retval new cbuf with the modified json
* @retval NULL Error
*/
static cbuf* yang_patch_xml2json_modified_cbuf(cxobj* x_simple_patch)
{
cbuf *json_simple_patch = cbuf_new();
if (json_simple_patch == NULL)
return NULL;
cbuf* cb = cbuf_new();
xml2json_cbuf(cb, x_simple_patch, 1);
// Insert a '[' after the first '{' to get the JSON to match what api_data_post/write() expect
char *json_simple_patch_tmp = cbuf_get(cb);
int brace_count = 0;
for (int l = 0; l < strlen(json_simple_patch_tmp); l++) {
char c = json_simple_patch_tmp[l];
if (c == '{') {
brace_count++;
if (brace_count == 2) { // We've reached the second brace, insert a '[' before it
cbuf_append(json_simple_patch,(int)'[');
}
}
cbuf_append(json_simple_patch,(int)c);
}
cbuf* json_simple_patch_2 = NULL;
// Insert a ']' before the last '}' to get the JSON to match what api_data_post() expects
for (int l = cbuf_len(json_simple_patch) - 1; l >= 0; l--) {
char c = cbuf_get(json_simple_patch)[l];
if (c == '}') {
// Truncate and add a string, as there is not a function to insert a char into a cbuf
json_simple_patch_2 = cbuf_trunc(json_simple_patch, l);
cbuf_append_str(json_simple_patch_2, "]}");
break;
}
}
cbuf_free(json_simple_patch);
cbuf_free(cb);
return json_simple_patch_2;
}
/*!yang_patch_strip_after_last_slash
*
* Strip /... from end of val
* so that e.g. "/interface=eth2" becomes "/"
* or "/interface_list=mylist/interface=eth2" becomes "/interface_list=mylist/"
*
* @param[in] val value to strip
* @retval new cbuf with the stripped string
* @retval NULL error
*/
static cbuf* yang_patch_strip_after_last_slash(cbuf* val)
{
cbuf *cb = cbuf_new();
cbuf* val_tmp = cbuf_new();
cbuf_append_str(val_tmp, cbuf_get(val));
int idx = cbuf_len(val_tmp);
for (int l = cbuf_len(val_tmp) - 1; l>= 0; l--) {
if (cbuf_get(val_tmp)[l] == '/') {
idx = l;
break;
}
}
if (idx == cbuf_len(val_tmp)) // Didn't find a slash in the loop above
return NULL;
cbuf* val_tmp_2 = cbuf_trunc(val_tmp, idx + 1);
if (cbuf_append_str(cb, cbuf_get(val_tmp_2)) < 0)
return NULL;
cbuf_free(val_tmp);
cbuf_free(val_tmp_2);
return cb;
}
/*! YANG PATCH replace method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* @param[in] simplepatch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces"
* @param[in] target_val value in "target" field of edit in YANG patch
* @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch
* @param[in] value_vec pointer to the "value" array of an edit in YANG patch
* @param[in] x_simple_patch pointer to XML containing module name, e.g. <ietf-interfaces:interface/>
*/
static int yang_patch_do_replace (
clicon_handle h,
void *req,
int pi,
cvec *qvec,
int pretty,
restconf_media media_out,
ietf_ds_t ds,
cbuf* simple_patch_request_uri,
cbuf* target_val,
int value_vec_len,
cxobj** value_vec,
cxobj *x_simple_patch
)
{
cxobj * value_vec_tmp = NULL;
cbuf* delete_req_uri = cbuf_new();
if (delete_req_uri == NULL)
return 1;
// Make delete_req_uri something like "/restconf/data/ietf-interfaces:interfaces"
if (cbuf_append_str(delete_req_uri, cbuf_get(simple_patch_request_uri)) < 0)
return 1;
// Add the target to delete_req_uri,
// so it's something like "/restconf/data/ietf-interfaces:interfaces/interface=eth2"
if (cbuf_append_str(delete_req_uri, cbuf_get(target_val)) < 0)
return 1;
// Delete the object with the old values
int ret = api_data_delete(h, req, cbuf_get(delete_req_uri), pi, pretty, YANG_DATA_JSON, ds );
cbuf_free(delete_req_uri);
if (ret != 0)
return ret;
// Now set up for the post request.
// Strip /... from end of target val
// so that e.g. "/interface=eth2" becomes "/"
// or "/interface_list=mylist/interface=eth2" becomes "/interface_list=mylist/"
cbuf* post_req_uri = yang_patch_strip_after_last_slash(target_val);
// Make post_req_uri something like "/restconf/data/ietf-interfaces:interfaces"
if (cbuf_append_str(simple_patch_request_uri, cbuf_get(post_req_uri)))
return 1;
cbuf_free(post_req_uri);
// Now insert the new values into the data
// (which will include the key value and all other mandatory values)
for (int k = 0; k < value_vec_len; k++) {
if (value_vec[k] != NULL) {
value_vec_tmp = xml_dup(value_vec[k]);
xml_addsub(x_simple_patch, value_vec_tmp);
}
}
// Convert the data to json
cbuf *json_simple_patch = cbuf_new();
if (json_simple_patch == NULL)
return 1;
xml2json_cbuf(json_simple_patch, x_simple_patch, 1);
// Send the POST request
ret = api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, ds );
cbuf_free(json_simple_patch);
xml_free(value_vec_tmp);
return ret;
}
/*! YANG PATCH create method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* @param[in] simplepatch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces"
* @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch
* @param[in] value_vec pointer to the "value" array of an edit in YANG patch
* @param[in] x_simple_patch pointer to XML containing module name, e.g. <ietf-interfaces:interface/>
*/
static int yang_patch_do_create (
clicon_handle h,
void *req,
int pi,
cvec *qvec,
int pretty,
restconf_media media_out,
ietf_ds_t ds,
cbuf* simple_patch_request_uri,
int value_vec_len,
cxobj** value_vec,
cxobj *x_simple_patch
)
{
cxobj * value_vec_tmp = NULL;
for (int k = 0; k < value_vec_len; k++) {
if (value_vec[k] != NULL) {
value_vec_tmp = xml_dup(value_vec[k]);
xml_addsub(x_simple_patch, value_vec_tmp);
}
}
// Send the POST request
cbuf* cb = cbuf_new();
xml2json_cbuf(cb, x_simple_patch, 1);
char *json_simple_patch = cbuf_get(cb);
int ret = api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds );
xml_free(value_vec_tmp);
return ret;
}
/*! YANG PATCH insert method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] pi Offset, where to start pcvec
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* @param[in] simple_patch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces"
* @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch
* @param[in] value_vec pointer to the "value" array of an edit in YANG patch
* @param[in] x_simple_patch pointer to XML containing module name, e.g. <ietf-interfaces:interface/>
* @param[in] where_val value in "where" field of edit in YANG patch
* @param[in] api_path full API path, e.g. "/restconf/data/example-jukebox:jukebox/playlist=Foo-One"
* @param[in] point_val value in "point" field of edit in YANG patch
*/
static int yang_patch_do_insert (
clicon_handle h,
void *req,
int pi,
int pretty,
restconf_media media_out,
ietf_ds_t ds,
cbuf* simple_patch_request_uri,
int value_vec_len,
cxobj** value_vec,
cxobj *x_simple_patch,
cbuf* where_val,
char* api_path,
cbuf *point_val
)
{
cxobj * value_vec_tmp = NULL;
// Loop through the XML, and get each value
for (int k = 0; k < value_vec_len; k++) {
if (value_vec[k] != NULL) {
value_vec_tmp = xml_dup(value_vec[k]);
xml_addsub(x_simple_patch, value_vec_tmp);
}
}
cbuf *json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch);
if (json_simple_patch == NULL)
return 1;
// Set the insert attributes
cvec* qvec_tmp = NULL;
qvec_tmp = cvec_new(0);
if (qvec_tmp == NULL)
return 1;
cg_var *cv;
if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){
return 1;
}
cv_name_set(cv, "insert");
cv_string_set(cv, cbuf_get(where_val));
cbuf *point_str = cbuf_new();
if (point_str == NULL)
return 1;
cbuf_append_str(point_str, api_path);
cbuf_append_str(point_str, cbuf_get(point_val));
if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){
return 1;
}
cv_name_set(cv, "point");
cv_string_set(cv, cbuf_get(point_str));
// Send the POST request
int ret = api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec_tmp, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, ds );
xml_free(value_vec_tmp);
cbuf_free(point_str);
cbuf_free(json_simple_patch);
return ret;
}
/*! YANG PATCH merge method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @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] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* @param[in] simple_patch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces"
* @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch
* @param[in] value_vec pointer to the "value" array of an edit in YANG patch
* @param[in] x_simple_patch pointer to XML containing module name, e.g. "<ietf-interfaces:interface/>"
* @param[in] where_val value in "where" field of edit in YANG patch
* @param[in] key_xn XML with key tag and value, e.g. "<name>Foo-One</name>"
*/
static int yang_patch_do_merge (
clicon_handle h,
void *req,
cvec *pcvec,
int pi,
cvec *qvec,
int pretty,
restconf_media media_out,
ietf_ds_t ds,
cbuf* simple_patch_request_uri,
int value_vec_len,
cxobj** value_vec,
cxobj *x_simple_patch,
cxobj *key_xn
)
{
int ret = -1;
cxobj * value_vec_tmp = NULL;
if (key_xn != NULL)
xml_addsub(x_simple_patch, key_xn);
// Loop through the XML, create JSON from each one, and submit a simple patch
for (int k = 0; k < value_vec_len; k++) {
if (value_vec[k] != NULL) {
value_vec_tmp = xml_dup(value_vec[k]);
xml_addsub(x_simple_patch, value_vec_tmp);
}
cbuf* cb = cbuf_new();
xml2json_cbuf(cb, x_simple_patch, 1);
cbuf *json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch);
if (json_simple_patch == NULL)
return 1;
xml_free(value_vec_tmp);
// Send the simple patch request
ret = api_data_write(h, req, cbuf_get(simple_patch_request_uri), pcvec, pi, qvec, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, 1, ds );
cbuf_free(cb);
cbuf_free(json_simple_patch);
}
return ret;
}
/*! YANG PATCH method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] api_path0 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 RFC8072
* YANG patch can be used to "create", "delete", "insert", "merge", "move", "replace", and/or
"remove" a resource within the target resource.
* Currently "move" not supported
*/
static int
api_data_yang_patch(clicon_handle h,
void *req,
char *api_path0,
cvec *pcvec,
int pi,
cvec *qvec,
char *data,
int pretty,
restconf_media media_out,
ietf_ds_t ds)
{
int retval = -1;
int i;
cxobj *xdata0 = NULL; /* Original -d data struct (including top symbol) */
cbuf *cbx = NULL;
cxobj *xtop = NULL; /* top of api-path */
cxobj *xbot = NULL; /* bottom of api-path */
yang_stmt *ybot = NULL; /* yang of xbot */
cxobj *xbot_tmp = NULL;
yang_stmt *yspec;
char *api_path;
cxobj *xret = NULL;
cxobj *xretcom = NULL; /* return from commit */
cxobj *xretdis = NULL; /* return from discard-changes */
cxobj *xerr = NULL; /* malloced must be freed */
int ret;
cvec *nsc = NULL;
yang_bind yb;
char *xpath = NULL;
cbuf *path_orig_1 = NULL;
clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path0);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
api_path=api_path0;
/* strip /... from start */
for (i=0; i<pi; i++)
api_path = index(api_path+1, '/');
/* Translate yang-patch path to xpath: xpath (cbpath) and namespace context (nsc) */
char yang_patch_path[] = "/ietf-yang-patch:yang-patch";
if ((ret = api_path2xpath(yang_patch_path, yspec, &xpath, &nsc, &xerr)) < 0)
goto done;
if (ret == 0){ /* validation failed */
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
/* Create config top-of-tree */
if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
goto done;
/* Translate yang-patch path to xml in the form of xtop/xbot */
xbot = xtop;
if ((ret = api_path2xml(yang_patch_path, yspec, xtop, YC_DATANODE, 1, &xbot, &ybot, &xerr)) < 0)
goto done;
if (ret == 0){ /* validation failed */
if (api_return_err(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
yb = YB_MODULE;
if ((ret = clixon_json_parse_string(data, yb, yspec, &xbot, &xerr)) < 0){
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if (ret == 0){
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
/*
* RFC 8072 2.1: The message-body MUST identify exactly one resource instance
*/
int nrchildren0 = 0;
cxobj *x = NULL;
if (xml_child_nr_type(xbot, CX_ELMNT) - nrchildren0 != 1){
if (netconf_malformed_message_xml(&xerr, "The message-body MUST contain exactly one instance of the expected data resource") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
size_t veclen;
cxobj **vec = NULL;
while ((x = xml_child_each(xbot, x, CX_ELMNT)) != NULL){
ret = xpath_vec(x, nsc, "edit", &vec, &veclen);
if (xml_flag(x, XML_FLAG_MARK)){
xml_flag_reset(x, XML_FLAG_MARK);
continue;
}
}
path_orig_1 = cbuf_new();
if (path_orig_1 == NULL) {
goto done;
} else {
cbuf_append_str(path_orig_1, restconf_uripath(h));
}
// Loop through the edits
for (int i = 0; i < veclen; i++) {
cxobj **tmp_vec = NULL;
size_t tmp_veclen = 0;
cxobj *xn = vec[i];
clicon_log_xml(LOG_DEBUG, xn, "%s %d xn:", __FUNCTION__, __LINE__);
// Get target
cbuf *target_val = cbuf_new();
ret = yang_patch_get_xval(nsc, xn, target_val, "target");
if (ret < 0) {
goto done;
}
// Get operation
cbuf *op_val = cbuf_new();
ret = yang_patch_get_xval(nsc, xn, op_val, "operation");
if (ret < 0) {
goto done;
}
// Get "point" and "where" for insert operations
cbuf *point_val = NULL;
cbuf *where_val = cbuf_new();
if (strcmp(cbuf_get(op_val), "insert") == 0) {
point_val = cbuf_new();
ret = yang_patch_get_xval(nsc, xn, point_val, "point");
if (ret < 0) {
goto done;
}
where_val = cbuf_new();
ret = yang_patch_get_xval(nsc, xn, where_val, "where");
if (ret < 0) {
goto done;
}
}
// Construct request URI
cbuf* simple_patch_request_uri = cbuf_new();
cbuf_append_str(simple_patch_request_uri, cbuf_get(path_orig_1));
cbuf* api_path_target = cbuf_new();
cbuf_append_str(api_path_target, api_path);
if (strcmp(cbuf_get(op_val), "merge") == 0) {
cbuf_append_str(api_path_target, cbuf_get(target_val));
cbuf_append_str(simple_patch_request_uri, cbuf_get(target_val));
}
if (xerr)
xml_free(xerr);
if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
goto done;
// Get key field
/* Translate api_path to xml in the form of xtop/xbot */
xbot_tmp = xtop;
if ((ret = api_path2xml(cbuf_get(api_path_target), yspec, xtop, YC_DATANODE, 1, &xbot_tmp, &ybot, &xerr)) < 0)
goto done;
if (ret == 0){ /* validation failed */
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
char *key_node_id = xml_name(xbot_tmp);
char *path = NULL;
if ((path = restconf_param_get(h, "REQUEST_URI")) != NULL){
for (int i1 = 0; i1 <pi; i1++)
path = index(path+1, '/');
}
const char colon[2] = ":";
char *modname = strtok(&(path[1]), colon);
cxobj **key_vec = NULL;
key_vec = xml_childvec_get(xbot_tmp);
cxobj *key_xn = NULL;
if (key_vec != NULL) {
key_xn = key_vec[0];
}
// Get values (for "delete" and "remove", there are no values)
xpath_vec(xn, nsc, "value", &tmp_vec, &tmp_veclen);
key_node_id = NULL;
// Loop through the values
for (int j = 0; j < tmp_veclen; j++) {
cxobj *values_xn = tmp_vec[j];
cxobj** values_child_vec = xml_childvec_get(values_xn);
if (key_node_id == NULL)
key_node_id = xml_name(*values_child_vec);
cbuf *patch_header = cbuf_new();
if (patch_header == NULL) {
goto done;
}
cbuf_append_str(patch_header, modname);
cbuf_append_str(patch_header, ":");
cbuf_append_str(patch_header, key_node_id);
cxobj *x_simple_patch = xml_new(cbuf_get(patch_header), NULL, CX_ELMNT);
if (x_simple_patch == NULL)
goto done;
int value_vec_len = xml_child_nr(*values_child_vec);
cxobj** value_vec = xml_childvec_get(*values_child_vec);
// For "replace", delete the item and then POST it
// TODO - in an ordered list, insert it into its original position
if (strcmp(cbuf_get(op_val),"replace") == 0) {
ret = yang_patch_do_replace(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, target_val, value_vec_len, value_vec, x_simple_patch);
if (ret != 0) {
goto done;
}
}
// For "create", put all the data values into a single POST request
if (strcmp(cbuf_get(op_val),"create") == 0) {
ret = yang_patch_do_create(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch);
if (ret != 0) {
goto done;
}
}
// For "insert", make a api_data_post request
if (strcmp(cbuf_get(op_val), "insert") == 0) {
ret = yang_patch_do_insert(h, req, pi, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch, where_val, api_path, point_val);
if (ret != 0) {
goto done;
}
}
// For merge", make single simple patch requests for each value
if (strcmp(cbuf_get(op_val),"merge") == 0) {
ret = yang_patch_do_merge(h, req, pcvec, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch, key_xn);
if (ret != 0) {
goto done;
}
}
cbuf_free(patch_header);
yang_patch_free_mem((void *)x_simple_patch); // Using xml_free() causes crash
}
if ((strcmp(cbuf_get(op_val), "delete") == 0) ||
(strcmp(cbuf_get(op_val), "remove") == 0)) {
cbuf_append_str(simple_patch_request_uri, cbuf_get(target_val));
if (strcmp(cbuf_get(op_val), "delete") == 0) {
// TODO - send error
} else {
// TODO - do not send error
}
api_data_delete(h, req, cbuf_get(simple_patch_request_uri), pi, pretty, YANG_DATA_JSON, ds);
}
cbuf_free(simple_patch_request_uri);
cbuf_free(api_path_target);
cbuf_free(target_val);
cbuf_free(op_val);
cbuf_free(point_val);
cbuf_free(where_val);
}
ok:
retval = 0;
done:
cbuf_free(path_orig_1);
yang_patch_free_mem((void *)vec);
yang_patch_free_mem((void *)xpath);
if (nsc)
xml_nsctx_free(nsc);
if (xret)
xml_free(xret);
if (xerr)
xml_free(xerr);
if (xretcom)
xml_free(xretcom);
if (xretdis)
xml_free(xretdis);
if (xtop)
xml_free(xtop);
if (xdata0)
xml_free(xdata0);
if (cbx)
cbuf_free(cbx);
return retval;
}
#endif // YANG_PATCH
/*! Generic REST PUT method /*! Generic REST PUT method
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] req Generic Www handle * @param[in] req Generic Www handle

View file

@ -39,11 +39,16 @@
#ifndef _RESTCONF_METHODS_H_ #ifndef _RESTCONF_METHODS_H_
#define _RESTCONF_METHODS_H_ #define _RESTCONF_METHODS_H_
#define TEMP_STR_MALLOC_SIZE 5000
/* /*
* Prototypes * Prototypes
*/ */
int api_data_options(clicon_handle h, void *req); int api_data_options(clicon_handle h, void *req);
int api_data_write(clicon_handle h, void *req, char *api_path0,
cvec *pcvec, int pi,
cvec *qvec, char *data,
int pretty, restconf_media media_in, restconf_media media_out,
int plain_patch, ietf_ds_t ds);
int api_data_put(clicon_handle h, void *req, char *api_path, int api_data_put(clicon_handle h, void *req, char *api_path,
cvec *pcvec, int pi, cvec *pcvec, int pi,
cvec *qvec, char *data, cvec *qvec, char *data,

View file

@ -0,0 +1,783 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate)
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 YANG PATCH implementation
*/
#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 <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 "restconf_lib.h"
#include "restconf_handle.h"
#include "restconf_api.h"
#include "restconf_err.h"
#include "restconf_methods.h"
#include "restconf_methods_post.h"
#include "restconf_methods_patch.h"
#ifdef YANG_PATCH
/*! Return a value within XML tags
* @param [in] nsc namespace context
* @param [in] xn cxobj containing XML with the current edit
* @param [in] val cbuf to which the value will be written
* @param [in] key string containing the tag
* @retval 0 success
* @retval <0 failure
*/
static int
yang_patch_get_xval(cvec *nsc,
cxobj *xn,
cbuf *val,
const char *key)
{
cxobj **vec = NULL;
size_t veclen = 0;
char* tmp_val = NULL;
int ret;
cxobj *xn_tmp = NULL;
if ((ret = xpath_vec(xn, nsc, "%s", &vec, &veclen, key)) < 0)
return ret;
if (veclen == 1){ //veclen should always be 1
xn_tmp = vec[0];
tmp_val = xml_body(xn_tmp);
cbuf_append_str(val, tmp_val);
}
return 0;
}
/*! Add square brackets after the surrounding curly brackets in JSON
* Needed, in order to modify the result of xml2json_cbuf() to be valid input
* to api_data_post() and api_data_write()
* @param [in] x_simple_patch a cxobj to pass to xml2json_cbuf()
* @retval new cbuf with the modified json
* @retval NULL Error
*/
static cbuf*
yang_patch_xml2json_modified_cbuf(cxobj *x_simple_patch)
{
cbuf *json_simple_patch = NULL;
cbuf *cb = NULL;
char *json_simple_patch_tmp;
int brace_count = 0;
json_simple_patch = cbuf_new();
if (json_simple_patch == NULL)
return NULL;
cb = cbuf_new();
xml2json_cbuf(cb, x_simple_patch, 1);
// Insert a '[' after the first '{' to get the JSON to match what api_data_post/write() expect
json_simple_patch_tmp = cbuf_get(cb);
for (int l = 0; l < strlen(json_simple_patch_tmp); l++) {
char c = json_simple_patch_tmp[l];
if (c == '{') {
brace_count++;
if (brace_count == 2) { // We've reached the second brace, insert a '[' before it
cbuf_append(json_simple_patch,(int)'[');
}
}
cbuf_append(json_simple_patch,(int)c);
}
// Insert a ']' before the last '}' to get the JSON to match what api_data_post() expects
for (int l = cbuf_len(json_simple_patch) - 1; l >= 0; l--) {
char c = cbuf_get(json_simple_patch)[l];
if (c == '}') {
// Truncate and add a string, as there is not a function to insert a char into a cbuf
cbuf_trunc(json_simple_patch, l);
cbuf_append_str(json_simple_patch, "]}");
break;
}
}
cbuf_free(cb);
return json_simple_patch;
}
/*!yang_patch_strip_after_last_slash
*
* Strip /... from end of val
* so that e.g. "/interface=eth2" becomes "/"
* or "/interface_list=mylist/interface=eth2" becomes "/interface_list=mylist/"
*
* @param[in] val value to strip
* @retval new cbuf with the stripped string
* @retval NULL error
*/
static cbuf*
yang_patch_strip_after_last_slash(cbuf* val)
{
cbuf *cb;
cbuf *val_tmp;
int idx;
cb = cbuf_new();
val_tmp = cbuf_new();
cbuf_append_str(val_tmp, cbuf_get(val));
idx = cbuf_len(val_tmp);
for (int l = cbuf_len(val_tmp) - 1; l>= 0; l--) {
if (cbuf_get(val_tmp)[l] == '/') {
idx = l;
break;
}
}
if (idx == cbuf_len(val_tmp)) // Didn't find a slash in the loop above
return NULL;
cbuf_trunc(val_tmp, idx + 1);
if (cbuf_append_str(cb, cbuf_get(val_tmp)) < 0)
return NULL;
cbuf_free(val_tmp);
return cb;
}
/*! YANG PATCH replace method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* @param[in] simplepatch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces"
* @param[in] target_val value in "target" field of edit in YANG patch
* @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch
* @param[in] value_vec pointer to the "value" array of an edit in YANG patch
* @param[in] x_simple_patch pointer to XML containing module name, e.g. <ietf-interfaces:interface/>
*/
static int
yang_patch_do_replace (clicon_handle h,
void *req,
int pi,
cvec *qvec,
int pretty,
restconf_media media_out,
ietf_ds_t ds,
cbuf *simple_patch_request_uri,
cbuf *target_val,
int value_vec_len,
cxobj **value_vec,
cxobj *x_simple_patch
)
{
cxobj *value_vec_tmp = NULL;
cbuf *delete_req_uri = NULL;
int ret;
cbuf *post_req_uri = NULL;
cbuf *json_simple_patch = NULL;
delete_req_uri = cbuf_new();
if (delete_req_uri == NULL)
return 1;
// Make delete_req_uri something like "/restconf/data/ietf-interfaces:interfaces"
if (cbuf_append_str(delete_req_uri, cbuf_get(simple_patch_request_uri)) < 0)
return 1;
// Add the target to delete_req_uri,
// so it's something like "/restconf/data/ietf-interfaces:interfaces/interface=eth2"
if (cbuf_append_str(delete_req_uri, cbuf_get(target_val)) < 0)
return 1;
// Delete the object with the old values
ret = api_data_delete(h, req, cbuf_get(delete_req_uri), pi, pretty, YANG_DATA_JSON, ds );
cbuf_free(delete_req_uri);
if (ret != 0)
return ret;
// Now set up for the post request.
// Strip /... from end of target val
// so that e.g. "/interface=eth2" becomes "/"
// or "/interface_list=mylist/interface=eth2" becomes "/interface_list=mylist/"
post_req_uri = yang_patch_strip_after_last_slash(target_val);
// Make post_req_uri something like "/restconf/data/ietf-interfaces:interfaces"
if (cbuf_append_str(simple_patch_request_uri, cbuf_get(post_req_uri)))
return 1;
cbuf_free(post_req_uri);
// Now insert the new values into the data
// (which will include the key value and all other mandatory values)
for (int k = 0; k < value_vec_len; k++) {
if (value_vec[k] != NULL) {
value_vec_tmp = xml_dup(value_vec[k]);
xml_addsub(x_simple_patch, value_vec_tmp);
}
}
// Convert the data to json
json_simple_patch = cbuf_new();
if (json_simple_patch == NULL)
return 1;
xml2json_cbuf(json_simple_patch, x_simple_patch, 1);
// Send the POST request
ret = api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, ds );
cbuf_free(json_simple_patch);
xml_free(value_vec_tmp);
return ret;
}
/*! YANG PATCH create method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* @param[in] simplepatch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces"
* @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch
* @param[in] value_vec pointer to the "value" array of an edit in YANG patch
* @param[in] x_simple_patch pointer to XML containing module name, e.g. <ietf-interfaces:interface/>
*/
static int
yang_patch_do_create (clicon_handle h,
void *req,
int pi,
cvec *qvec,
int pretty,
restconf_media media_out,
ietf_ds_t ds,
cbuf *simple_patch_request_uri,
int value_vec_len,
cxobj **value_vec,
cxobj *x_simple_patch
)
{
int retval = -1;
cxobj *value_vec_tmp = NULL;
cbuf *cb = NULL;
char *json_simple_patch;
for (int k = 0; k < value_vec_len; k++) {
if (value_vec[k] != NULL) {
value_vec_tmp = xml_dup(value_vec[k]);
xml_addsub(x_simple_patch, value_vec_tmp);
}
}
// Send the POST request
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (xml2json_cbuf(cb, x_simple_patch, 1) < 0)
goto done;
json_simple_patch = cbuf_get(cb);
if (api_data_post(h, req, cbuf_get(simple_patch_request_uri),
pi, qvec,
json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds) < 0)
goto done;
xml_free(value_vec_tmp);
retval = 0;
done:
return retval;
}
/*! YANG PATCH insert method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] pi Offset, where to start pcvec
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* @param[in] simple_patch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces"
* @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch
* @param[in] value_vec pointer to the "value" array of an edit in YANG patch
* @param[in] x_simple_patch pointer to XML containing module name, e.g. <ietf-interfaces:interface/>
* @param[in] where_val value in "where" field of edit in YANG patch
* @param[in] api_path full API path, e.g. "/restconf/data/example-jukebox:jukebox/playlist=Foo-One"
* @param[in] point_val value in "point" field of edit in YANG patch
*/
static int
yang_patch_do_insert (clicon_handle h,
void *req,
int pi,
int pretty,
restconf_media media_out,
ietf_ds_t ds,
cbuf *simple_patch_request_uri,
int value_vec_len,
cxobj **value_vec,
cxobj *x_simple_patch,
cbuf *where_val,
char *api_path,
cbuf *point_val
)
{
cxobj *value_vec_tmp = NULL;
cbuf *json_simple_patch;
cg_var *cv;
cbuf *point_str = NULL;
int ret;
// Loop through the XML, and get each value
for (int k = 0; k < value_vec_len; k++) {
if (value_vec[k] != NULL) {
value_vec_tmp = xml_dup(value_vec[k]);
xml_addsub(x_simple_patch, value_vec_tmp);
}
}
json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch);
if (json_simple_patch == NULL)
return 1;
// Set the insert attributes
cvec* qvec_tmp = NULL;
qvec_tmp = cvec_new(0);
if (qvec_tmp == NULL)
return 1;
if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){
return 1;
}
cv_name_set(cv, "insert");
cv_string_set(cv, cbuf_get(where_val));
point_str = cbuf_new();
if (point_str == NULL)
return 1;
cbuf_append_str(point_str, api_path);
cbuf_append_str(point_str, cbuf_get(point_val));
if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){
return 1;
}
cv_name_set(cv, "point");
cv_string_set(cv, cbuf_get(point_str));
// Send the POST request
ret = api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec_tmp, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, ds );
xml_free(value_vec_tmp);
cbuf_free(point_str);
cbuf_free(json_simple_patch);
return ret;
}
/*! YANG PATCH merge method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @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] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* @param[in] simple_patch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces"
* @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch
* @param[in] value_vec pointer to the "value" array of an edit in YANG patch
* @param[in] x_simple_patch pointer to XML containing module name, e.g. "<ietf-interfaces:interface/>"
* @param[in] where_val value in "where" field of edit in YANG patch
* @param[in] key_xn XML with key tag and value, e.g. "<name>Foo-One</name>"
*/
static int
yang_patch_do_merge(clicon_handle h,
void *req,
cvec *pcvec,
int pi,
cvec *qvec,
int pretty,
restconf_media media_out,
ietf_ds_t ds,
cbuf* simple_patch_request_uri,
int value_vec_len,
cxobj** value_vec,
cxobj *x_simple_patch,
cxobj *key_xn
)
{
int ret = -1;
cxobj *value_vec_tmp = NULL;
cbuf *cb = NULL;
cbuf *json_simple_patch = NULL;
if (key_xn != NULL)
xml_addsub(x_simple_patch, key_xn);
// Loop through the XML, create JSON from each one, and submit a simple patch
for (int k = 0; k < value_vec_len; k++) {
if (value_vec[k] != NULL) {
value_vec_tmp = xml_dup(value_vec[k]);
xml_addsub(x_simple_patch, value_vec_tmp);
}
cb = cbuf_new();
xml2json_cbuf(cb, x_simple_patch, 1);
json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch);
if (json_simple_patch == NULL)
return 1;
xml_free(value_vec_tmp);
// Send the simple patch request
ret = api_data_write(h, req, cbuf_get(simple_patch_request_uri), pcvec, pi, qvec, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, 1, ds );
cbuf_free(cb);
cbuf_free(json_simple_patch);
}
return ret;
}
/*! YANG PATCH method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] api_path0 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 RFC8072
* YANG patch can be used to "create", "delete", "insert", "merge", "move", "replace", and/or
"remove" a resource within the target resource.
* Currently "move" not supported
*/
int
api_data_yang_patch(clicon_handle h,
void *req,
char *api_path0,
cvec *pcvec,
int pi,
cvec *qvec,
char *data,
int pretty,
restconf_media media_out,
ietf_ds_t ds)
{
int retval = -1;
int i;
cxobj *xdata0 = NULL; /* Original -d data struct (including top symbol) */
cbuf *cbx = NULL;
cxobj *xtop = NULL; /* top of api-path */
cxobj *xbot = NULL; /* bottom of api-path */
yang_stmt *ybot = NULL; /* yang of xbot */
cxobj *xbot_tmp = NULL;
yang_stmt *yspec;
char *api_path;
cxobj *xret = NULL;
cxobj *xretcom = NULL; /* return from commit */
cxobj *xretdis = NULL; /* return from discard-changes */
cxobj *xerr = NULL; /* malloced must be freed */
int ret;
cvec *nsc = NULL;
yang_bind yb;
char *xpath = NULL;
cbuf *path_orig_1 = NULL;
char yang_patch_path[] = "/ietf-yang-patch:yang-patch";
int nrchildren0 = 0;
cxobj *x = NULL;
size_t veclen;
cxobj **vec = NULL;
clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path0);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
api_path=api_path0;
/* strip /... from start */
for (i=0; i<pi; i++)
api_path = index(api_path+1, '/');
/* Translate yang-patch path to xpath: xpath (cbpath) and namespace context (nsc) */
if ((ret = api_path2xpath(yang_patch_path, yspec, &xpath, &nsc, &xerr)) < 0)
goto done;
if (ret == 0){ /* validation failed */
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
/* Create config top-of-tree */
if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
goto done;
/* Translate yang-patch path to xml in the form of xtop/xbot */
xbot = xtop;
if ((ret = api_path2xml(yang_patch_path, yspec, xtop, YC_DATANODE, 1, &xbot, &ybot, &xerr)) < 0)
goto done;
if (ret == 0){ /* validation failed */
if (api_return_err(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
yb = YB_MODULE;
if ((ret = clixon_json_parse_string(data, yb, yspec, &xbot, &xerr)) < 0){
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if (ret == 0){
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
/*
* RFC 8072 2.1: The message-body MUST identify exactly one resource instance
*/
if (xml_child_nr_type(xbot, CX_ELMNT) - nrchildren0 != 1){
if (netconf_malformed_message_xml(&xerr, "The message-body MUST contain exactly one instance of the expected data resource") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
while ((x = xml_child_each(xbot, x, CX_ELMNT)) != NULL){
ret = xpath_vec(x, nsc, "edit", &vec, &veclen);
if (xml_flag(x, XML_FLAG_MARK)){
xml_flag_reset(x, XML_FLAG_MARK);
continue;
}
}
path_orig_1 = cbuf_new();
if (path_orig_1 == NULL) {
goto done;
} else {
cbuf_append_str(path_orig_1, restconf_uripath(h));
}
// Loop through the edits
for (int i = 0; i < veclen; i++) {
cxobj **tmp_vec = NULL;
size_t tmp_veclen = 0;
cxobj *xn = vec[i];
clicon_log_xml(LOG_DEBUG, xn, "%s %d xn:", __FUNCTION__, __LINE__);
// Get target
cbuf *target_val = cbuf_new();
ret = yang_patch_get_xval(nsc, xn, target_val, "target");
if (ret < 0) {
goto done;
}
// Get operation
cbuf *op_val = cbuf_new();
ret = yang_patch_get_xval(nsc, xn, op_val, "operation");
if (ret < 0) {
goto done;
}
// Get "point" and "where" for insert operations
cbuf *point_val = NULL;
cbuf *where_val = cbuf_new();
if (strcmp(cbuf_get(op_val), "insert") == 0) {
point_val = cbuf_new();
ret = yang_patch_get_xval(nsc, xn, point_val, "point");
if (ret < 0) {
goto done;
}
where_val = cbuf_new();
ret = yang_patch_get_xval(nsc, xn, where_val, "where");
if (ret < 0) {
goto done;
}
}
// Construct request URI
cbuf* simple_patch_request_uri = cbuf_new();
cbuf_append_str(simple_patch_request_uri, cbuf_get(path_orig_1));
cbuf* api_path_target = cbuf_new();
cbuf_append_str(api_path_target, api_path);
if (strcmp(cbuf_get(op_val), "merge") == 0) {
cbuf_append_str(api_path_target, cbuf_get(target_val));
cbuf_append_str(simple_patch_request_uri, cbuf_get(target_val));
}
if (xerr)
xml_free(xerr);
if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
goto done;
// Get key field
/* Translate api_path to xml in the form of xtop/xbot */
xbot_tmp = xtop;
if ((ret = api_path2xml(cbuf_get(api_path_target), yspec, xtop, YC_DATANODE, 1, &xbot_tmp, &ybot, &xerr)) < 0)
goto done;
if (ret == 0){ /* validation failed */
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
char *key_node_id = xml_name(xbot_tmp);
char *path = NULL;
if ((path = restconf_param_get(h, "REQUEST_URI")) != NULL){
for (int i1 = 0; i1 <pi; i1++)
path = index(path+1, '/');
}
const char colon[2] = ":";
char *modname = strtok(&(path[1]), colon);
cxobj **key_vec = NULL;
key_vec = xml_childvec_get(xbot_tmp);
cxobj *key_xn = NULL;
if (key_vec != NULL) {
key_xn = key_vec[0];
}
// Get values (for "delete" and "remove", there are no values)
xpath_vec(xn, nsc, "value", &tmp_vec, &tmp_veclen);
key_node_id = NULL;
// Loop through the values
for (int j = 0; j < tmp_veclen; j++) {
cxobj *values_xn = tmp_vec[j];
cxobj** values_child_vec = xml_childvec_get(values_xn);
if (key_node_id == NULL)
key_node_id = xml_name(*values_child_vec);
cbuf *patch_header = cbuf_new();
if (patch_header == NULL) {
goto done;
}
cbuf_append_str(patch_header, modname);
cbuf_append_str(patch_header, ":");
cbuf_append_str(patch_header, key_node_id);
cxobj *x_simple_patch = xml_new(cbuf_get(patch_header), NULL, CX_ELMNT);
if (x_simple_patch == NULL)
goto done;
int value_vec_len = xml_child_nr(*values_child_vec);
cxobj** value_vec = xml_childvec_get(*values_child_vec);
// For "replace", delete the item and then POST it
// TODO - in an ordered list, insert it into its original position
if (strcmp(cbuf_get(op_val),"replace") == 0) {
ret = yang_patch_do_replace(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, target_val, value_vec_len, value_vec, x_simple_patch);
if (ret != 0) {
goto done;
}
}
// For "create", put all the data values into a single POST request
if (strcmp(cbuf_get(op_val),"create") == 0) {
ret = yang_patch_do_create(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch);
if (ret != 0) {
goto done;
}
}
// For "insert", make a api_data_post request
if (strcmp(cbuf_get(op_val), "insert") == 0) {
ret = yang_patch_do_insert(h, req, pi, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch, where_val, api_path, point_val);
if (ret != 0) {
goto done;
}
}
// For merge", make single simple patch requests for each value
if (strcmp(cbuf_get(op_val),"merge") == 0) {
ret = yang_patch_do_merge(h, req, pcvec, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch, key_xn);
if (ret != 0) {
goto done;
}
}
cbuf_free(patch_header);
if (x_simple_patch)
free(x_simple_patch);
}
if ((strcmp(cbuf_get(op_val), "delete") == 0) ||
(strcmp(cbuf_get(op_val), "remove") == 0)) {
cbuf_append_str(simple_patch_request_uri, cbuf_get(target_val));
if (strcmp(cbuf_get(op_val), "delete") == 0) {
// TODO - send error
} else {
// TODO - do not send error
}
api_data_delete(h, req, cbuf_get(simple_patch_request_uri), pi, pretty, YANG_DATA_JSON, ds);
}
cbuf_free(simple_patch_request_uri);
cbuf_free(api_path_target);
cbuf_free(target_val);
cbuf_free(op_val);
cbuf_free(point_val);
cbuf_free(where_val);
}
ok:
retval = 0;
done:
cbuf_free(path_orig_1);
if (vec)
free(vec);
if (xpath)
free(xpath);
if (nsc)
xml_nsctx_free(nsc);
if (xret)
xml_free(xret);
if (xerr)
xml_free(xerr);
if (xretcom)
xml_free(xretcom);
if (xretdis)
xml_free(xretdis);
if (xtop)
xml_free(xtop);
if (xdata0)
xml_free(xdata0);
if (cbx)
cbuf_free(cbx);
return retval;
}
#else // YANG_PATCH
int
api_data_yang_patch(clicon_handle h,
void *req,
char *api_path0,
cvec *pcvec,
int pi,
cvec *qvec,
char *data,
int pretty,
restconf_media media_out,
ietf_ds_t ds)
{
clicon_err(OE_RESTCONF, 0, "Not implemented");
return -1;
}
#endif // YANG_PATCH

View file

@ -0,0 +1,50 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate)
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 YANG PATCH implementation
*/
#ifndef _RESTCONF_METHODS_PATCH_H_
#define _RESTCONF_METHODS_PATCH_H_
/*
* Prototypes
*/
int api_data_yang_patch(clicon_handle h, void *req, char *api_path0,
cvec *pcvec, int pi,
cvec *qvec, char *data,
int pretty, restconf_media media_out, ietf_ds_t ds);
#endif /* _RESTCONF_METHODS_PATCH_H_ */

View file

@ -125,6 +125,8 @@ http_location_header(clicon_handle h,
* @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] media_out Output media * @param[in] media_out Output media
* @retval 0 OK
* @retval -1 Error
* 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

View file

@ -178,10 +178,12 @@ api_root_restconf_exact(clicon_handle h,
goto done; goto done;
switch (media_out){ switch (media_out){
case YANG_DATA_XML: case YANG_DATA_XML:
case YANG_PATCH_XML:
if (clicon_xml2cbuf(cb, xt, 0, pretty, -1) < 0) if (clicon_xml2cbuf(cb, xt, 0, pretty, -1) < 0)
goto done; goto done;
break; break;
case YANG_DATA_JSON: case YANG_DATA_JSON:
case YANG_PATCH_JSON:
if (xml2json_cbuf(cb, xt, pretty) < 0) if (xml2json_cbuf(cb, xt, pretty) < 0)
goto done; goto done;
break; break;
@ -256,10 +258,12 @@ api_yang_library_version(clicon_handle h,
} }
switch (media_out){ switch (media_out){
case YANG_DATA_XML: case YANG_DATA_XML:
case YANG_PATCH_XML:
if (clicon_xml2cbuf(cb, xt, 0, pretty, -1) < 0) if (clicon_xml2cbuf(cb, xt, 0, pretty, -1) < 0)
goto done; goto done;
break; break;
case YANG_DATA_JSON: case YANG_DATA_JSON:
case YANG_PATCH_JSON:
if (xml2json_cbuf(cb, xt, pretty) < 0) if (xml2json_cbuf(cb, xt, pretty) < 0)
goto done; goto done;
break; break;

View file

@ -1,6 +1,7 @@
# README for Clixon developers # README for Clixon developers
* [Code documentation](#documentation) * [Code documentation](#documentation)
* [C style](#c-style)
* [How to work in git (how-to-work-in-git)](#how-to-work-in-git) * [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)
@ -28,6 +29,81 @@ How to document the code
*/ */
``` ```
## C style
Clixon uses 4-char indentation, a la emacs "cc-mode".
### Function declarations
Functions in C code are written as follows:
```
static int
myfn(int par1,
my_structure *par2)
{
int retval = -1;
my_structure *ms;
ms = NULL;
```
Notes:
1. the return type of the function and all qualifers on first line (`static int`)
2. function name and first parameter on second line, thereafter each parameter on own line
3. Each parameter indented to match the "longest" (`my_structure`)
4. Pointer declarations written: `type *p`, not: `type* p`.
5. All local variables in a function declared at top of function, not inline with C-statements.
6. Local variables can be initialized with scalars or constants, not eg malloc or functions with return values that need to be checked for errors
7. There is a single empty line between local variable declarations and the first function statement.
Function signatures are declared in include files or in forward declaration using "one-line" syntax, unless very long:
```
static int myfn(int par1, my_structure *par2);
```
### Errors
Errors are typically declared as follows:
```
if (myfn(0) < 0){
clicon_err(OE_UNIX, EINVAL, "myfn");
goto done;
}
```
All function returns that have return values must be checked
Default return values form a function are:
0: OK
-1: Fatal Error
In some cases, Clixon uses three-value returns as follows:
1: OK
0: Invalid
-1: Fatal error
### Return values
Clixon uses goto:s only to get a single point of exit functions as follows:
```
{
int retval = -1;
...
retval = 0;
done:
return retval
}
```
Notes:
1. Use only a single return statement in a function
2. Do not use of goto:s in other ways
### Comments
Use `/* */`. Use `//` only for temporal comments.
## How to work in git ## How to work in git
Clixon uses semantic versioning (https://semver.org). Clixon uses semantic versioning (https://semver.org).

View file

@ -1,234 +0,0 @@
#!/usr/bin/env bash
# clixon example
# Assumes fexample is set to name of yang file
cat <<EOF2 > $fexample
module clixon-example {
yang-version 1.1;
namespace "urn:example:clixon";
prefix ex;
import ietf-interfaces {
/* is in yang/optional which means clixon must be installed using --opt-yang-installdir */
prefix if;
}
import ietf-ip {
prefix ip;
}
import iana-if-type {
prefix ianaift;
}
import ietf-datastores {
prefix ds;
}
description
"Clixon example used as a part of the Clixon test suite.
It can be used as a basis for making new Clixon applications.
Note, may change without updating revision, just for testing current master.
";
revision 2020-12-01 {
description "Added table/paramater/value as the primary data example";
}
revision 2020-03-11 {
description "Added container around translation list. Released in Clixon 4.4.0";
}
revision 2019-11-05 {
description "Augment interface. Released in Clixon 4.3.0";
}
revision 2019-07-23 {
description "Extension e4. Released in Clixon 4.1.0";
}
revision 2019-01-13 {
description "Released in Clixon 3.9";
}
/* Example interface type for tests, local callbacks, etc */
identity eth {
base if:interface-type;
}
identity loopback {
base if:interface-type;
}
/* Generic config data */
container table{
list parameter{
key name;
leaf name{
type string;
}
leaf value{
type string;
}
leaf stat{
description "Inline state data for example application";
config false;
type int32;
}
}
}
/* State data (not config) for the example application*/
container state {
config false;
description "state data for the example application (must be here for example get operation)";
leaf-list op {
type string;
}
}
augment "/if:interfaces/if:interface" {
container my-status {
config false;
description "For testing augment+state";
leaf int {
type int32;
}
leaf str {
type string;
}
}
}
/* yang extension implemented by the example backend code. */
extension e4 {
description
"The first child of the ex:e4 (unknown) statement is inserted into
the module as a regular data statement. This means that 'uses bar;'
in the ex:e4 statement below is a valid data node";
argument arg;
}
grouping bar {
leaf bar{
type string;
}
}
ex:e4 arg1{
uses bar;
}
/* Example notification as used in RFC 5277 and RFC 8040 */
notification event {
description "Example notification event.";
leaf event-class {
type string;
description "Event class identifier.";
}
container reportingEntity {
description "Event specific information.";
leaf card {
type string;
description "Line card identifier.";
}
}
leaf severity {
type string;
description "Event severity description.";
}
}
rpc client-rpc {
description "Example local client-side RPC that is processed by the
the netconf/restconf and not sent to the backend.
This is a clixon implementation detail: some rpc:s
are better processed by the client for API or perf reasons";
input {
leaf x {
type string;
}
}
output {
leaf x {
type string;
}
}
}
rpc empty {
description "Smallest possible RPC with no input or output sections";
}
rpc optional {
description "Small RPC with optional input and output";
input {
leaf x {
type string;
}
}
output {
leaf x {
type string;
}
}
}
rpc example {
description "Some example input/output for testing RFC7950 7.14.
RPC simply echoes the input for debugging.";
input {
leaf x {
description
"If a leaf in the input tree has a 'mandatory' statement with
the value 'true', the leaf MUST be present in an RPC invocation.";
type string;
mandatory true;
}
leaf y {
description
"If a leaf in the input tree has a 'mandatory' statement with the
value 'true', the leaf MUST be present in an RPC invocation.";
type string;
default "42";
}
leaf-list z {
description
"If a leaf-list in the input tree has one or more default
values, the server MUST use these values (XXX not supported)";
type string;
}
leaf w {
description
"If any node has a 'when' statement that would evaluate to
'false',then this node MUST NOT be present in the input tree.
(XXX not supported)";
type string;
}
list u0 {
description "list without key";
leaf uk{
type string;
}
}
list u1 {
description "list with key";
key uk;
leaf uk{
type string;
}
leaf val{
type string;
}
}
}
output {
leaf x {
type string;
}
leaf y {
type string;
}
leaf z {
type string;
}
leaf w {
type string;
}
list u0 {
leaf uk{
type string;
}
}
list u1 {
key uk;
leaf uk{
type string;
}
leaf val{
type string;
}
}
}
}
}
EOF2

File diff suppressed because it is too large Load diff

View file

@ -218,8 +218,10 @@ expectpart "$(curl -u andy:bar $CURLOPTS -X PATCH -H 'Content-Type: application/
new "Check content (xml)" new "Check content (xml)"
expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" '<jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Clash</name><album><name>London Calling</name><genre>jazz</genre><year>1979</year></album></artist></library></jukebox>' expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" '<jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Clash</name><album><name>London Calling</name><genre>jazz</genre><year>1979</year></album></artist></library></jukebox>'
if false; then # It is being implemented
new "not implemented media type" new "not implemented media type"
expectpart "$(curl -u andy:bar $CURLOPTS -X PATCH -H 'Content-Type: application/yang-patch+xml' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '<album xmlns="http://example.com/ns/example-jukebox"><name>London Calling</name><genre>jazz</genre></album>')" 0 "HTTP/$HVER 501" expectpart "$(curl -u andy:bar $CURLOPTS -X PATCH -H 'Content-Type: application/yang-patch+xml' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '<album xmlns="http://example.com/ns/example-jukebox"><name>London Calling</name><genre>jazz</genre></album>')" 0 "HTTP/$HVER 501"
fi
new "wrong media type" new "wrong media type"
expectpart "$(curl -u andy:bar $CURLOPTS -X PATCH -H 'Content-Type: text/html' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '<album xmlns="http://example.com/ns/example-jukebox"><name>London Calling</name><genre>jazz</genre></album>')" 0 "HTTP/$HVER 415" expectpart "$(curl -u andy:bar $CURLOPTS -X PATCH -H 'Content-Type: text/html' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '<album xmlns="http://example.com/ns/example-jukebox"><name>London Calling</name><genre>jazz</genre></album>')" 0 "HTTP/$HVER 415"

View file

@ -7,8 +7,9 @@
# 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
#echo "...skipped: YANG_PATCH JSON NYI" # Enable if YANG_PATCH
#if [ "$s" = $0 ]; then exit 0; else return 0; fi echo "...skipped: YANG_PATCH JSON NYI"
if [ "$s" = $0 ]; then exit 0; else return 0; fi
APPNAME=example APPNAME=example
@ -16,8 +17,6 @@ cfg=$dir/conf.xml
startupdb=$dir/startup_db startupdb=$dir/startup_db
fjukebox=$dir/example-jukebox.yang fjukebox=$dir/example-jukebox.yang
fyangpatch=$dir/ietf-yang-patch.yang fyangpatch=$dir/ietf-yang-patch.yang
finterfaces=$dir/ietf-interfaces.yang
fexample=$dir/clixon-example.yang
# Define default restconfig config: RESTCONFIG # Define default restconfig config: RESTCONFIG
RESTCONFIG=$(restconf_config user false) RESTCONFIG=$(restconf_config user false)
@ -91,6 +90,13 @@ cat <<EOF > $dir/example-system.yang
module example-system { module example-system {
namespace "http://example.com/ns/example-system"; namespace "http://example.com/ns/example-system";
prefix "ex"; prefix "ex";
import iana-if-type {
prefix ianaift;
}
import ietf-interfaces {
/* is in yang/optional which means clixon must be installed using --opt-yang-installdir */
prefix if;
}
import ietf-netconf-acm { import ietf-netconf-acm {
prefix nacm; prefix nacm;
} }
@ -108,12 +114,6 @@ EOF
# Yang Patch spec (fyangpatch must be set) # Yang Patch spec (fyangpatch must be set)
. ./yang-patch.sh . ./yang-patch.sh
# Interfaces spec (finterfaces must be set)
. ./interfaces.sh
# clixon example spec (fexample must be set)
. ./example.sh
# Common Jukebox spec (fjukebox must be set) # Common Jukebox spec (fjukebox must be set)
. ./jukebox.sh . ./jukebox.sh
@ -157,7 +157,7 @@ REQ='{
"interface": [ "interface": [
{ {
"name": "eth1", "name": "eth1",
"type": "clixon-example:eth", "type": "iana-if-type:atm",
"enabled": "false" "enabled": "false"
} }
] ]
@ -171,7 +171,7 @@ REQ='{
"interface": [ "interface": [
{ {
"name": "eth2", "name": "eth2",
"type": "clixon-example:eth", "type": "iana-if-type:atm",
"enabled": "false" "enabled": "false"
} }
] ]
@ -185,7 +185,7 @@ REQ='{
"interface": [ "interface": [
{ {
"name": "eth4", "name": "eth4",
"type": "clixon-example:eth", "type": "iana-if-type:atm",
"enabled": "false" "enabled": "false"
} }
] ]
@ -212,22 +212,22 @@ REQ='{
} }
}' }'
new "RFC 8072 YANG Patch JSON: Error." new "RFC 8072 YANG Patch JSON: Error."
expectpart "$(curl -u andy:bar $CURLOPTS -X PATCH -H 'Content-Type: application/yang-patch+json' -H 'Accept: application/yang-patch+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces -d "$REQ")" 0 "HTTP/$HVER 204 No Content" expectpart "$(curl -u andy:bar $CURLOPTS -X PATCH -H 'Content-Type: application/yang-patch+json' -H 'Accept: application/yang-patch+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces -d "$REQ")" 0 "HTTP/$HVER 204"
# #
# Create artist in jukebox example # Create artist in jukebox example
REQ='{"example-jukebox:artist":[{"name":"Foo Fighters"}]}' REQ='{"example-jukebox:artist":[{"name":"Foo Fighters"}]}'
new "RFC 8072 YANG Patch JSON jukebox example 1: Error." new "RFC 8072 YANG Patch JSON jukebox example 1: Error."
expectpart "$(curl -u andy:bar $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library -d "$REQ")" 0 "HTTP/$HVER 201 Created" expectpart "$(curl -u andy:bar $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library -d "$REQ")" 0 "HTTP/$HVER 201"
# Create album in jukebox example # Create album in jukebox example
REQ='<album xmlns="http://example.com/ns/example-jukebox"><name>Wasting Light</name><year>2011</year></album>' REQ='<album xmlns="http://example.com/ns/example-jukebox"><name>Wasting Light</name><year>2011</year></album>'
new "RFC 8072 YANG Patch JSON jukebox example 2: Error." new "RFC 8072 YANG Patch JSON jukebox example 2: Error."
expectpart "$(curl -u andy:bar $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d "$REQ")" 0 "HTTP/$HVER 201 Created" expectpart "$(curl -u andy:bar $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d "$REQ")" 0 "HTTP/$HVER 201"
# Add fields to album in jukebox example # Add fields to album in jukebox example
REQ='{"example-jukebox:album":[{"name":"Wasting Light","genre":"example-jukebox:alternative","year":2011}]}' REQ='{"example-jukebox:album":[{"name":"Wasting Light","genre":"example-jukebox:alternative","year":2011}]}'
new "RFC 8072 YANG Patch JSON jukebox example 3: Error." new "RFC 8072 YANG Patch JSON jukebox example 3: Error."
expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d "$REQ")" 0 "HTTP/$HVER 204 No Content" expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d "$REQ")" 0 "HTTP/$HVER 204"
# Uncomment to get info about album in jukebox example # Uncomment to get info about album in jukebox example
#new "RFC 8072 YANG Patch jukebox example get 2: Error." #new "RFC 8072 YANG Patch jukebox example get 2: Error."
@ -236,22 +236,22 @@ expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H 'Content-Type: application/ya
# Add songs to playlist in jukebox example # Add songs to playlist in jukebox example
REQ="{\"example-jukebox:song\":[{\"index\":1,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Rope']\"}]}" REQ="{\"example-jukebox:song\":[{\"index\":1,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Rope']\"}]}"
new "RFC 8072 YANG Patch JSON jukebox example 4: Error." new "RFC 8072 YANG Patch JSON jukebox example 4: Error."
expectpart "$(curl -u andy:bar $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$REQ")" 0 "HTTP/$HVER 201 Created" expectpart "$(curl -u andy:bar $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$REQ")" 0 "HTTP/$HVER 201"
# Add song at end of playlist # Add song at end of playlist
REQ="{\"example-jukebox:song\":[{\"index\":2,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Bridge Burning']\"}]}" REQ="{\"example-jukebox:song\":[{\"index\":2,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Bridge Burning']\"}]}"
new "RFC 8072 YANG Patch JSON jukebox example 5: Error." new "RFC 8072 YANG Patch JSON jukebox example 5: Error."
expectpart "$(curl -u andy:bar $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=last -d "$REQ")" 0 "HTTP/$HVER 201 Created" expectpart "$(curl -u andy:bar $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=last -d "$REQ")" 0 "HTTP/$HVER 201"
# Add song at end of playlist # Add song at end of playlist
REQ="{\"example-jukebox:song\":[{\"index\":4,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Still More Rope']\"}]}" REQ="{\"example-jukebox:song\":[{\"index\":4,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Still More Rope']\"}]}"
new "RFC 8072 YANG Patch JSON jukebox example 6: Error." new "RFC 8072 YANG Patch JSON jukebox example 6: Error."
expectpart "$(curl -u andy:bar $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=last -d "$REQ")" 0 "HTTP/$HVER 201 Created" expectpart "$(curl -u andy:bar $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=last -d "$REQ")" 0 "HTTP/$HVER 201"
# Add song at end of playlist # Add song at end of playlist
REQ="{\"example-jukebox:song\":[{\"index\":3,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='More Rope']\"}]}" REQ="{\"example-jukebox:song\":[{\"index\":3,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='More Rope']\"}]}"
new "RFC 8072 YANG Patch JSON jukebox example 7: Error." new "RFC 8072 YANG Patch JSON jukebox example 7: Error."
expectpart "$(curl -u andy:bar $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=last -d "$REQ")" 0 "HTTP/$HVER 201 Created" expectpart "$(curl -u andy:bar $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=last -d "$REQ")" 0 "HTTP/$HVER 201"
# Run YANG patch on the playlist, testing "insert after" and "insert before" # Run YANG patch on the playlist, testing "insert after" and "insert before"
REQ='{ REQ='{
@ -307,7 +307,7 @@ REQ='{
} }
}' }'
new "RFC 8072 YANG Patch JSON jukebox example: Error." new "RFC 8072 YANG Patch JSON jukebox example: Error."
expectpart "$(curl -u andy:bar $CURLOPTS -X PATCH -H 'Content-Type: application/yang-patch+json' -H 'Accept: application/yang-patch+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -d "$REQ")" 0 "HTTP/$HVER 201 Created" expectpart "$(curl -u andy:bar $CURLOPTS -X PATCH -H 'Content-Type: application/yang-patch+json' -H 'Accept: application/yang-patch+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -d "$REQ")" 0 "HTTP/$HVER 201"
# Uncomment to get info about playlist in jukebox example # Uncomment to get info about playlist in jukebox example
#new "RFC 8072 YANG Patch jukebox example get : Error." #new "RFC 8072 YANG Patch jukebox example get : Error."

View file

@ -7,8 +7,8 @@
# 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
#echo "...skipped: YANG_PATCH XML NYI" echo "...skipped: YANG_PATCH XML NYI"
#if [ "$s" = $0 ]; then exit 0; else return 0; fi if [ "$s" = $0 ]; then exit 0; else return 0; fi
APPNAME=example APPNAME=example