- Refactor yang patch code according to Clixon coding style
- Fixed all memory leaks
This commit is contained in:
parent
e3d0566d3c
commit
d1ed4ecd7d
8 changed files with 495 additions and 811 deletions
|
|
@ -591,7 +591,7 @@ api_data_write(clicon_handle h,
|
|||
* @param[in] data Stream input data
|
||||
* @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
|
||||
* @note restconf PUT is mapped to edit-config replace.
|
||||
* @see RFC8040 Sec 4.5 PUT
|
||||
* @see api_data_post
|
||||
|
|
@ -645,6 +645,7 @@ api_data_put(clicon_handle h,
|
|||
* @param[in] data Stream input data
|
||||
* @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
|
||||
* 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
|
||||
|
|
@ -678,7 +679,7 @@ api_data_patch(clicon_handle h,
|
|||
case YANG_PATCH_XML:
|
||||
#ifdef YANG_PATCH
|
||||
ret = api_data_yang_patch(h, req, api_path0, pcvec, pi, qvec, data, pretty,
|
||||
media_out, ds);
|
||||
media_in, media_out, ds);
|
||||
#else
|
||||
ret = restconf_notimplemented(h, req, pretty, media_out);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -318,6 +318,7 @@ api_data_get2(clicon_handle h,
|
|||
* @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
|
||||
*
|
||||
* The HEAD method is sent by the client to retrieve just the header fields
|
||||
* that would be returned for the comparable GET method, without the
|
||||
|
|
@ -348,11 +349,10 @@ api_data_head(clicon_handle h,
|
|||
* @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 RFC8527 datastore
|
||||
* @code
|
||||
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
|
||||
* @endcode
|
||||
* XXX: cant find a way to use Accept request field to choose Content-Type
|
||||
* I would like to support both xml and json.
|
||||
* Request may contain
|
||||
* Accept: application/yang.data+json,application/yang.data+xml
|
||||
* Response contains one of:
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate)
|
||||
Copyright (C) 2021 Siklu Ltd (YANG patch code)
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -32,7 +33,7 @@
|
|||
|
||||
***** END LICENSE BLOCK *****
|
||||
|
||||
* Restconf YANG PATCH implementation
|
||||
* Restconf YANG PATCH implementation (RFC8072)
|
||||
*/
|
||||
|
||||
|
||||
|
|
@ -68,42 +69,42 @@
|
|||
|
||||
#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;
|
||||
enum yang_patch_op{
|
||||
YANG_PATCH_OP_CREATE,
|
||||
YANG_PATCH_OP_DELETE,
|
||||
YANG_PATCH_OP_INSERT,
|
||||
YANG_PATCH_OP_MERGE,
|
||||
YANG_PATCH_OP_MOVE,
|
||||
YANG_PATCH_OP_REPLACE,
|
||||
YANG_PATCH_OP_REMOVE
|
||||
};
|
||||
typedef enum yang_patch_op yang_patch_op_t;
|
||||
|
||||
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;
|
||||
/* Yang patch operations according to RFC 8072
|
||||
*/
|
||||
static const map_str2int yang_patch_op_map[] = {
|
||||
{"create", YANG_PATCH_OP_CREATE},
|
||||
{"delete", YANG_PATCH_OP_DELETE},
|
||||
{"insert", YANG_PATCH_OP_INSERT},
|
||||
{"merge", YANG_PATCH_OP_MERGE},
|
||||
{"move", YANG_PATCH_OP_MOVE},
|
||||
{"replace", YANG_PATCH_OP_REPLACE},
|
||||
{"remove", YANG_PATCH_OP_REMOVE},
|
||||
{NULL, -1}
|
||||
};
|
||||
|
||||
static const yang_patch_op_t
|
||||
yang_patch_op2int(char *op)
|
||||
{
|
||||
return clicon_str2int(yang_patch_op_map, op);
|
||||
}
|
||||
|
||||
/*! 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
|
||||
* @param[in] x_simple_patch a cxobj to pass to xml2json_cbuf()
|
||||
* @retva cbuf With the modified json
|
||||
* @retva NULL Error
|
||||
*/
|
||||
static cbuf*
|
||||
yang_patch_xml2json_modified_cbuf(cxobj *x_simple_patch)
|
||||
|
|
@ -153,11 +154,11 @@ yang_patch_xml2json_modified_cbuf(cxobj *x_simple_patch)
|
|||
* or "/interface_list=mylist/interface=eth2" becomes "/interface_list=mylist/"
|
||||
*
|
||||
* @param[in] val value to strip
|
||||
* @retval new cbuf with the stripped string
|
||||
* @retval cbuf with the stripped string
|
||||
* @retval NULL error
|
||||
*/
|
||||
static cbuf*
|
||||
yang_patch_strip_after_last_slash(cbuf* val)
|
||||
yang_patch_strip_after_last_slash(char* val)
|
||||
{
|
||||
cbuf *cb;
|
||||
cbuf *val_tmp;
|
||||
|
|
@ -165,7 +166,7 @@ yang_patch_strip_after_last_slash(cbuf* val)
|
|||
|
||||
cb = cbuf_new();
|
||||
val_tmp = cbuf_new();
|
||||
cbuf_append_str(val_tmp, cbuf_get(val));
|
||||
cbuf_append_str(val_tmp, val);
|
||||
idx = cbuf_len(val_tmp);
|
||||
for (int l = cbuf_len(val_tmp) - 1; l>= 0; l--) {
|
||||
if (cbuf_get(val_tmp)[l] == '/') {
|
||||
|
|
@ -197,7 +198,7 @@ yang_patch_strip_after_last_slash(cbuf* val)
|
|||
* @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,
|
||||
yang_patch_do_replace(clicon_handle h,
|
||||
void *req,
|
||||
int pi,
|
||||
cvec *qvec,
|
||||
|
|
@ -205,48 +206,55 @@ yang_patch_do_replace (clicon_handle h,
|
|||
restconf_media media_out,
|
||||
ietf_ds_t ds,
|
||||
cbuf *simple_patch_request_uri,
|
||||
cbuf *target_val,
|
||||
char *target_val,
|
||||
int value_vec_len,
|
||||
cxobj **value_vec,
|
||||
cxobj *x_simple_patch
|
||||
)
|
||||
{
|
||||
int retval = -1;
|
||||
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;
|
||||
|
||||
if ((delete_req_uri = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
if ((json_simple_patch = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
// 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;
|
||||
if (cbuf_append_str(delete_req_uri, cbuf_get(simple_patch_request_uri)) < 0){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_append_str");
|
||||
goto done;
|
||||
}
|
||||
|
||||
// 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;
|
||||
if (cbuf_append_str(delete_req_uri, target_val) < 0){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_append_str");
|
||||
goto done;
|
||||
}
|
||||
|
||||
// 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;
|
||||
if (api_data_delete(h, req, cbuf_get(delete_req_uri), pi, pretty, YANG_DATA_JSON, ds) < 0)
|
||||
goto done;
|
||||
|
||||
// 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);
|
||||
if ((post_req_uri = yang_patch_strip_after_last_slash(target_val)) == NULL)
|
||||
goto done;
|
||||
|
||||
// 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);
|
||||
|
||||
if (cbuf_append_str(simple_patch_request_uri, cbuf_get(post_req_uri)) < 0){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_append_str");
|
||||
goto done;
|
||||
}
|
||||
// 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++) {
|
||||
|
|
@ -256,17 +264,22 @@ yang_patch_do_replace (clicon_handle h,
|
|||
}
|
||||
}
|
||||
// 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 );
|
||||
|
||||
if (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 ) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
if (post_req_uri)
|
||||
cbuf_free(post_req_uri);
|
||||
if (delete_req_uri)
|
||||
cbuf_free(delete_req_uri);
|
||||
if (json_simple_patch)
|
||||
cbuf_free(json_simple_patch);
|
||||
if (value_vec_tmp)
|
||||
xml_free(value_vec_tmp);
|
||||
return ret;
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! YANG PATCH create method
|
||||
|
|
@ -283,7 +296,7 @@ yang_patch_do_replace (clicon_handle h,
|
|||
* @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,
|
||||
yang_patch_do_create(clicon_handle h,
|
||||
void *req,
|
||||
int pi,
|
||||
cvec *qvec,
|
||||
|
|
@ -299,29 +312,29 @@ yang_patch_do_create (clicon_handle h,
|
|||
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;
|
||||
}
|
||||
for (int k = 0; k < value_vec_len; k++) {
|
||||
if (value_vec[k] != NULL) {
|
||||
if ((value_vec_tmp = xml_dup(value_vec[k])) == NULL)
|
||||
goto done;
|
||||
xml_addsub(x_simple_patch, value_vec_tmp);
|
||||
}
|
||||
}
|
||||
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)
|
||||
cbuf_get(cb), pretty, YANG_DATA_JSON, media_out, ds) < 0)
|
||||
goto done;
|
||||
xml_free(value_vec_tmp);
|
||||
retval = 0;
|
||||
done:
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
|
@ -341,7 +354,7 @@ yang_patch_do_create (clicon_handle h,
|
|||
* @param[in] point_val value in "point" field of edit in YANG patch
|
||||
*/
|
||||
static int
|
||||
yang_patch_do_insert (clicon_handle h,
|
||||
yang_patch_do_insert(clicon_handle h,
|
||||
void *req,
|
||||
int pi,
|
||||
int pretty,
|
||||
|
|
@ -351,17 +364,26 @@ yang_patch_do_insert (clicon_handle h,
|
|||
int value_vec_len,
|
||||
cxobj **value_vec,
|
||||
cxobj *x_simple_patch,
|
||||
cbuf *where_val,
|
||||
char *where_val,
|
||||
char *api_path,
|
||||
cbuf *point_val
|
||||
char *point_val
|
||||
)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *value_vec_tmp = NULL;
|
||||
cbuf *json_simple_patch;
|
||||
cg_var *cv;
|
||||
cbuf *point_str = NULL;
|
||||
int ret;
|
||||
cvec *qvec_tmp = NULL;
|
||||
|
||||
if ((point_str = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
if ((qvec_tmp = cvec_new(0)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
// Loop through the XML, and get each value
|
||||
for (int k = 0; k < value_vec_len; k++) {
|
||||
if (value_vec[k] != NULL) {
|
||||
|
|
@ -369,38 +391,39 @@ yang_patch_do_insert (clicon_handle h,
|
|||
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;
|
||||
if ((json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch)) == NULL)
|
||||
goto done;
|
||||
|
||||
// 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;
|
||||
clicon_err(OE_UNIX, errno, "cvec_add");
|
||||
goto done;
|
||||
}
|
||||
cv_name_set(cv, "insert");
|
||||
cv_string_set(cv, cbuf_get(where_val));
|
||||
point_str = cbuf_new();
|
||||
if (point_str == NULL)
|
||||
return 1;
|
||||
if (where_val)
|
||||
cv_string_set(cv, where_val);
|
||||
cbuf_append_str(point_str, api_path);
|
||||
cbuf_append_str(point_str, cbuf_get(point_val));
|
||||
if (point_val)
|
||||
cbuf_append_str(point_str, point_val);
|
||||
if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){
|
||||
return 1;
|
||||
clicon_err(OE_UNIX, errno, "cvec_add");
|
||||
goto done;
|
||||
}
|
||||
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);
|
||||
if (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)< 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
if (qvec_tmp)
|
||||
cvec_free(qvec_tmp);
|
||||
if (point_str)
|
||||
cbuf_free(point_str);
|
||||
if (json_simple_patch)
|
||||
cbuf_free(json_simple_patch);
|
||||
return ret;
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! YANG PATCH merge method
|
||||
|
|
@ -428,18 +451,22 @@ yang_patch_do_merge(clicon_handle h,
|
|||
int pretty,
|
||||
restconf_media media_out,
|
||||
ietf_ds_t ds,
|
||||
cbuf* simple_patch_request_uri,
|
||||
cbuf *simple_patch_request_uri,
|
||||
int value_vec_len,
|
||||
cxobj** value_vec,
|
||||
cxobj **value_vec,
|
||||
cxobj *x_simple_patch,
|
||||
cxobj *key_xn
|
||||
)
|
||||
{
|
||||
int ret = -1;
|
||||
int retval = -1;
|
||||
cxobj *value_vec_tmp = NULL;
|
||||
cbuf *cb = NULL;
|
||||
cbuf *json_simple_patch = NULL;
|
||||
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
if (key_xn != NULL)
|
||||
xml_addsub(x_simple_patch, key_xn);
|
||||
|
||||
|
|
@ -449,19 +476,251 @@ yang_patch_do_merge(clicon_handle h,
|
|||
value_vec_tmp = xml_dup(value_vec[k]);
|
||||
xml_addsub(x_simple_patch, value_vec_tmp);
|
||||
}
|
||||
cb = cbuf_new();
|
||||
cbuf_reset(cb); /* reuse cb */
|
||||
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);
|
||||
if ((json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch)) == NULL)
|
||||
goto done;
|
||||
// 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);
|
||||
if (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 ) < 0)
|
||||
goto done;
|
||||
if (json_simple_patch){
|
||||
cbuf_free(json_simple_patch);
|
||||
json_simple_patch = NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
if (json_simple_patch)
|
||||
cbuf_free(json_simple_patch);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Process a single yang patch "edit/value" element
|
||||
*
|
||||
* @param[in] xn XML value element
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
yang_patch_do_value(clicon_handle h,
|
||||
void *req,
|
||||
cvec *pcvec,
|
||||
int pi,
|
||||
cvec *qvec,
|
||||
int pretty,
|
||||
restconf_media media_out,
|
||||
ietf_ds_t ds,
|
||||
cxobj *xn,
|
||||
char *modname,
|
||||
yang_patch_op_t operation,
|
||||
char *where_val,
|
||||
char *point_val,
|
||||
cbuf *simple_patch_request_uri,
|
||||
char *target_val,
|
||||
char *api_path,
|
||||
cxobj *key_xn
|
||||
)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj **values_child_vec;
|
||||
char *key_node_id;
|
||||
cbuf *patch_header = NULL;
|
||||
cxobj *x_simple_patch = NULL;
|
||||
int value_vec_len;
|
||||
cxobj **value_vec;
|
||||
|
||||
values_child_vec = xml_childvec_get(xn);
|
||||
key_node_id = xml_name(*values_child_vec);
|
||||
/* Create cbufs:s */
|
||||
if ((patch_header = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
cprintf(patch_header, "%s:%s", modname, key_node_id);
|
||||
if ((x_simple_patch = xml_new(cbuf_get(patch_header), NULL, CX_ELMNT)) == NULL)
|
||||
goto done;
|
||||
value_vec_len = xml_child_nr(*values_child_vec);
|
||||
value_vec = xml_childvec_get(*values_child_vec);
|
||||
switch (operation){
|
||||
case YANG_PATCH_OP_REPLACE:
|
||||
if (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) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case YANG_PATCH_OP_CREATE:
|
||||
if (yang_patch_do_create(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case YANG_PATCH_OP_INSERT:
|
||||
if (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) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case YANG_PATCH_OP_MERGE:
|
||||
if (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) < 0)
|
||||
goto done;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
if (x_simple_patch)
|
||||
xml_free(x_simple_patch);
|
||||
if (patch_header)
|
||||
cbuf_free(patch_header);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Process a single yang patch "edit" element
|
||||
*
|
||||
* @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] xn XML edit element
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
yang_patch_do_edit(clicon_handle h,
|
||||
void *req,
|
||||
cvec *pcvec,
|
||||
int pi,
|
||||
cvec *qvec,
|
||||
int pretty,
|
||||
restconf_media media_out,
|
||||
ietf_ds_t ds,
|
||||
yang_stmt *yspec,
|
||||
cxobj *xn,
|
||||
char *uripath0,
|
||||
char *api_path
|
||||
)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj **vec = NULL;
|
||||
size_t veclen = 0;
|
||||
int ret;
|
||||
cxobj *xerr = NULL; /* malloced must be freed */
|
||||
cxobj *xtop;
|
||||
cxobj *xbot = NULL;
|
||||
yang_patch_op_t operation;
|
||||
char *where_val = NULL;
|
||||
char *point_val = NULL;
|
||||
char *target_val = NULL;
|
||||
char *modname;
|
||||
cxobj **key_vec = NULL;
|
||||
cxobj *key_xn = NULL;
|
||||
int i;
|
||||
cxobj *x; /* general purpose xml-tree pointer */
|
||||
cbuf *simple_patch_request_uri = NULL;
|
||||
cbuf *api_path_target = NULL;
|
||||
yang_stmt *ybot = NULL;
|
||||
yang_stmt *ymod;
|
||||
|
||||
|
||||
clicon_log_xml(LOG_DEBUG, xn, "%s %d xn:", __FUNCTION__, __LINE__);
|
||||
/* Create cbufs:s */
|
||||
if ((simple_patch_request_uri = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
if ((api_path_target = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
if ((x = xpath_first(xn, NULL, "target")) == NULL){
|
||||
clicon_err(OE_YANG, 0, "target mandatory element not found");
|
||||
goto done;
|
||||
}
|
||||
target_val = xml_body(x);
|
||||
if ((x = xpath_first(xn, NULL, "operation")) == NULL){
|
||||
clicon_err(OE_YANG, 0, "operation mandatory element not found");
|
||||
goto done;
|
||||
}
|
||||
operation = yang_patch_op2int(xml_body(x));
|
||||
/* target and operation are mandatory */
|
||||
if (target_val == NULL){
|
||||
clicon_err(OE_YANG, 0, "operation/target: mandatory element not found");
|
||||
goto done;
|
||||
}
|
||||
if (operation == YANG_PATCH_OP_INSERT){
|
||||
if ((x = xpath_first(xn, NULL, "point")) != NULL)
|
||||
point_val = xml_body(x);
|
||||
if ((x = xpath_first(xn, NULL, "where")) != NULL)
|
||||
where_val = xml_body(x);
|
||||
if (point_val == NULL || where_val == NULL){
|
||||
clicon_err(OE_YANG, 0, "point/where: expected element not found");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
// Construct request URI
|
||||
cprintf(simple_patch_request_uri, "%s", uripath0);
|
||||
cprintf(api_path_target, "%s", api_path);
|
||||
if (operation == YANG_PATCH_OP_MERGE) {
|
||||
cbuf_append_str(api_path_target, target_val);
|
||||
cbuf_append_str(simple_patch_request_uri, target_val);
|
||||
}
|
||||
|
||||
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 = xtop;
|
||||
if ((ret = api_path2xml(cbuf_get(api_path_target), yspec, xtop, YC_DATANODE, 1, &xbot, &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;
|
||||
}
|
||||
/* Get module name */
|
||||
if (ys_real_module(ybot, &ymod) < 0)
|
||||
goto done;
|
||||
modname = yang_argument_get(ymod);
|
||||
// XXX this seems to be hardcoded to a yang list?
|
||||
if ((key_vec = xml_childvec_get(xbot)) != NULL)
|
||||
key_xn = key_vec[0];
|
||||
// Get values (for "delete" and "remove", there are no values)
|
||||
xpath_vec(xn, NULL, "value", &vec, &veclen);
|
||||
|
||||
// Loop through the values
|
||||
for (i = 0; i < veclen; i++) {
|
||||
if (yang_patch_do_value(h, req, pcvec, pi, qvec, pretty, media_out, ds,
|
||||
vec[i], modname,
|
||||
operation, where_val, point_val, simple_patch_request_uri, target_val,
|
||||
api_path, key_xn) < 0)
|
||||
goto done;
|
||||
}
|
||||
if (operation == YANG_PATCH_OP_DELETE ||
|
||||
operation == YANG_PATCH_OP_REMOVE){
|
||||
cbuf_append_str(simple_patch_request_uri, target_val);
|
||||
if (operation == YANG_PATCH_OP_DELETE) {
|
||||
// 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);
|
||||
}
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
if (vec)
|
||||
free(vec);
|
||||
if (simple_patch_request_uri)
|
||||
cbuf_free(simple_patch_request_uri);
|
||||
if (api_path_target)
|
||||
cbuf_free(api_path_target);
|
||||
if (xtop)
|
||||
xml_free(xtop);
|
||||
if (xerr)
|
||||
xml_free(xerr);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! YANG PATCH method
|
||||
|
|
@ -473,9 +732,13 @@ yang_patch_do_merge(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
|
||||
* @param[in] media_out Output media
|
||||
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* Netconf: <edit-config> (nc:operation="merge")
|
||||
* See RFC8072
|
||||
* @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
|
||||
|
|
@ -489,31 +752,18 @@ api_data_yang_patch(clicon_handle h,
|
|||
cvec *qvec,
|
||||
char *data,
|
||||
int pretty,
|
||||
restconf_media media_in,
|
||||
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;
|
||||
cxobj *xpatch = 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;
|
||||
char *uripath0 = NULL;
|
||||
size_t veclen;
|
||||
cxobj **vec = NULL;
|
||||
|
||||
|
|
@ -526,31 +776,27 @@ api_data_yang_patch(clicon_handle h,
|
|||
/* 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)
|
||||
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 (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;
|
||||
switch (media_in){
|
||||
case YANG_PATCH_XML:
|
||||
ret = clixon_xml_parse_string(data, YB_MODULE, yspec, &xpatch, &xerr);
|
||||
break;
|
||||
case YANG_PATCH_JSON: /* RFC 8072 patch */
|
||||
ret = clixon_json_parse_string(data, YB_MODULE, yspec, &xpatch, &xerr);
|
||||
break;
|
||||
default:
|
||||
restconf_unsupported_media(h, req, pretty, media_out);
|
||||
goto ok;
|
||||
break;
|
||||
}
|
||||
|
||||
yb = YB_MODULE;
|
||||
if ((ret = clixon_json_parse_string(data, yb, yspec, &xbot, &xerr)) < 0){
|
||||
/* Common error handling for json/xml parsing above */
|
||||
if (ret < 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)
|
||||
|
|
@ -565,201 +811,38 @@ api_data_yang_patch(clicon_handle h,
|
|||
/*
|
||||
* RFC 8072 2.1: The message-body MUST identify exactly one resource instance
|
||||
*/
|
||||
if (xml_child_nr_type(xbot, CX_ELMNT) - nrchildren0 != 1){
|
||||
if (xml_child_nr_type(xpatch, CX_ELMNT) != 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) {
|
||||
/* Save original uripath (will be changed by sub-calls) */
|
||||
if ((uripath0 = restconf_uripath(h)) == 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) {
|
||||
/* Find all edit operations and loop over them
|
||||
*/
|
||||
if (xpath_vec(xpatch, NULL, "yang-patch/edit", &vec, &veclen) < 0)
|
||||
goto done;
|
||||
}
|
||||
// Get operation
|
||||
cbuf *op_val = cbuf_new();
|
||||
ret = yang_patch_get_xval(nsc, xn, op_val, "operation");
|
||||
if (ret < 0) {
|
||||
for (i = 0; i < veclen; i++) {
|
||||
if (yang_patch_do_edit(h, req, pcvec, pi, qvec, pretty, media_out, ds,
|
||||
yspec,
|
||||
vec[i],
|
||||
uripath0, api_path) < 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 (uripath0)
|
||||
free(uripath0);
|
||||
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);
|
||||
if (xpatch)
|
||||
xml_free(xpatch);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
|
@ -774,6 +857,7 @@ api_data_yang_patch(clicon_handle h,
|
|||
cvec *qvec,
|
||||
char *data,
|
||||
int pretty,
|
||||
restconf_media media_in,
|
||||
restconf_media media_out,
|
||||
ietf_ds_t ds)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate)
|
||||
Copyright (C) 2021 Siklu Ltd (YANG patch code)
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -32,7 +33,7 @@
|
|||
|
||||
***** END LICENSE BLOCK *****
|
||||
|
||||
* Restconf YANG PATCH implementation
|
||||
* Restconf YANG PATCH implementation (RFC8072)
|
||||
*/
|
||||
|
||||
|
||||
|
|
@ -45,6 +46,7 @@
|
|||
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 pretty, restconf_media media_in, restconf_media media_out,
|
||||
ietf_ds_t ds);
|
||||
|
||||
#endif /* _RESTCONF_METHODS_PATCH_H_ */
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ http_location_header(clicon_handle h,
|
|||
* @param[in] data Stream input data
|
||||
* @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
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* restconf POST is mapped to edit-config create.
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ APPNAME=example
|
|||
cfg=$dir/conf.xml
|
||||
startupdb=$dir/startup_db
|
||||
fjukebox=$dir/example-jukebox.yang
|
||||
fyangpatch=$dir/ietf-yang-patch.yang
|
||||
|
||||
# Define default restconfig config: RESTCONFIG
|
||||
RESTCONFIG=$(restconf_config user false)
|
||||
|
|
@ -111,9 +110,6 @@ cat <<EOF > $dir/example-system.yang
|
|||
}
|
||||
EOF
|
||||
|
||||
# Yang Patch spec (fyangpatch must be set)
|
||||
. ./yang-patch.sh
|
||||
|
||||
# Common Jukebox spec (fjukebox must be set)
|
||||
. ./jukebox.sh
|
||||
|
||||
|
|
@ -15,7 +15,6 @@ APPNAME=example
|
|||
cfg=$dir/conf.xml
|
||||
startupdb=$dir/startup_db
|
||||
fjukebox=$dir/example-jukebox.yang
|
||||
fyangpatch=$dir/ietf-yang-patch.yang
|
||||
finterfaces=$dir/ietf-interfaces.yang
|
||||
fexample=$dir/clixon-example.yang
|
||||
|
||||
|
|
@ -105,9 +104,6 @@ cat <<EOF > $dir/example-system.yang
|
|||
}
|
||||
EOF
|
||||
|
||||
# Yang Patch spec (fyangpatch must be set)
|
||||
. ./yang-patch.sh
|
||||
|
||||
# Interfaces spec (finterfaces must be set)
|
||||
. ./interfaces.sh
|
||||
|
||||
|
|
|
|||
|
|
@ -1,396 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
# for rfc 8072
|
||||
# Assumes fyangpatch is set to name of yang file
|
||||
|
||||
cat <<EOF1 > $fyangpatch
|
||||
module ietf-yang-patch {
|
||||
yang-version 1.1;
|
||||
namespace "urn:ietf:params:xml:ns:yang:ietf-yang-patch";
|
||||
prefix "ypatch";
|
||||
|
||||
import ietf-restconf { prefix rc; }
|
||||
|
||||
organization
|
||||
"IETF NETCONF (Network Configuration) Working Group";
|
||||
|
||||
contact
|
||||
"WG Web: <https://datatracker.ietf.org/wg/netconf/>
|
||||
WG List: <mailto:netconf@ietf.org>
|
||||
|
||||
Author: Andy Bierman
|
||||
<mailto:andy@yumaworks.com>
|
||||
|
||||
Author: Martin Bjorklund
|
||||
<mailto:mbj@tail-f.com>
|
||||
|
||||
Author: Kent Watsen
|
||||
<mailto:kwatsen@juniper.net>";
|
||||
|
||||
description
|
||||
"This module contains conceptual YANG specifications
|
||||
for the YANG Patch and YANG Patch Status data structures.
|
||||
|
||||
Note that the YANG definitions within this module do not
|
||||
represent configuration data of any kind.
|
||||
The YANG grouping statements provide a normative syntax
|
||||
for XML and JSON message-encoding purposes.
|
||||
|
||||
Copyright (c) 2017 IETF Trust and the persons identified as
|
||||
authors of the code. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or
|
||||
without modification, is permitted pursuant to, and subject
|
||||
to the license terms contained in, the Simplified BSD License
|
||||
set forth in Section 4.c of the IETF Trust's Legal Provisions
|
||||
Relating to IETF Documents
|
||||
(http://trustee.ietf.org/license-info).
|
||||
|
||||
This version of this YANG module is part of RFC 8072; see
|
||||
the RFC itself for full legal notices.";
|
||||
|
||||
revision 2017-02-22 {
|
||||
description
|
||||
"Initial revision.";
|
||||
reference
|
||||
"RFC 8072: YANG Patch Media Type.";
|
||||
}
|
||||
|
||||
typedef target-resource-offset {
|
||||
type string;
|
||||
description
|
||||
"Contains a data resource identifier string representing
|
||||
a sub-resource within the target resource.
|
||||
The document root for this expression is the
|
||||
target resource that is specified in the
|
||||
protocol operation (e.g., the URI for the PATCH request).
|
||||
|
||||
This string is encoded according to the same rules as those
|
||||
for a data resource identifier in a RESTCONF request URI.";
|
||||
reference
|
||||
"RFC 8040, Section 3.5.3.";
|
||||
}
|
||||
|
||||
rc:yang-data "yang-patch" {
|
||||
uses yang-patch;
|
||||
}
|
||||
|
||||
rc:yang-data "yang-patch-status" {
|
||||
uses yang-patch-status;
|
||||
}
|
||||
|
||||
grouping yang-patch {
|
||||
|
||||
description
|
||||
"A grouping that contains a YANG container representing the
|
||||
syntax and semantics of a YANG Patch edit request message.";
|
||||
|
||||
container yang-patch {
|
||||
description
|
||||
"Represents a conceptual sequence of datastore edits,
|
||||
called a patch. Each patch is given a client-assigned
|
||||
patch identifier. Each edit MUST be applied
|
||||
in ascending order, and all edits MUST be applied.
|
||||
If any errors occur, then the target datastore MUST NOT
|
||||
be changed by the YANG Patch operation.
|
||||
|
||||
It is possible for a datastore constraint violation to occur
|
||||
due to any node in the datastore, including nodes not
|
||||
included in the 'edit' list. Any validation errors MUST
|
||||
be reported in the reply message.";
|
||||
|
||||
reference
|
||||
"RFC 7950, Section 8.3.";
|
||||
|
||||
leaf patch-id {
|
||||
type string;
|
||||
mandatory true;
|
||||
description
|
||||
"An arbitrary string provided by the client to identify
|
||||
the entire patch. Error messages returned by the server
|
||||
that pertain to this patch will be identified by this
|
||||
'patch-id' value. A client SHOULD attempt to generate
|
||||
unique 'patch-id' values to distinguish between
|
||||
transactions from multiple clients in any audit logs
|
||||
maintained by the server.";
|
||||
}
|
||||
|
||||
leaf comment {
|
||||
type string;
|
||||
description
|
||||
"An arbitrary string provided by the client to describe
|
||||
the entire patch. This value SHOULD be present in any
|
||||
audit logging records generated by the server for the
|
||||
patch.";
|
||||
}
|
||||
|
||||
list edit {
|
||||
key edit-id;
|
||||
ordered-by user;
|
||||
|
||||
description
|
||||
"Represents one edit within the YANG Patch request message.
|
||||
The 'edit' list is applied in the following manner:
|
||||
|
||||
- The first edit is conceptually applied to a copy
|
||||
of the existing target datastore, e.g., the
|
||||
running configuration datastore.
|
||||
- Each ascending edit is conceptually applied to
|
||||
the result of the previous edit(s).
|
||||
- After all edits have been successfully processed,
|
||||
the result is validated according to YANG constraints.
|
||||
- If successful, the server will attempt to apply
|
||||
the result to the target datastore.";
|
||||
|
||||
leaf edit-id {
|
||||
type string;
|
||||
description
|
||||
"Arbitrary string index for the edit.
|
||||
Error messages returned by the server that pertain
|
||||
to a specific edit will be identified by this value.";
|
||||
}
|
||||
|
||||
leaf operation {
|
||||
type enumeration {
|
||||
enum create {
|
||||
description
|
||||
"The target data node is created using the supplied
|
||||
value, only if it does not already exist. The
|
||||
'target' leaf identifies the data node to be
|
||||
created, not the parent data node.";
|
||||
}
|
||||
enum delete {
|
||||
description
|
||||
"Delete the target node, only if the data resource
|
||||
currently exists; otherwise, return an error.";
|
||||
}
|
||||
|
||||
enum insert {
|
||||
description
|
||||
"Insert the supplied value into a user-ordered
|
||||
list or leaf-list entry. The target node must
|
||||
represent a new data resource. If the 'where'
|
||||
parameter is set to 'before' or 'after', then
|
||||
the 'point' parameter identifies the insertion
|
||||
point for the target node.";
|
||||
}
|
||||
enum merge {
|
||||
description
|
||||
"The supplied value is merged with the target data
|
||||
node.";
|
||||
}
|
||||
enum move {
|
||||
description
|
||||
"Move the target node. Reorder a user-ordered
|
||||
list or leaf-list. The target node must represent
|
||||
an existing data resource. If the 'where' parameter
|
||||
is set to 'before' or 'after', then the 'point'
|
||||
parameter identifies the insertion point to move
|
||||
the target node.";
|
||||
}
|
||||
enum replace {
|
||||
description
|
||||
"The supplied value is used to replace the target
|
||||
data node.";
|
||||
}
|
||||
enum remove {
|
||||
description
|
||||
"Delete the target node if it currently exists.";
|
||||
}
|
||||
}
|
||||
mandatory true;
|
||||
description
|
||||
"The datastore operation requested for the associated
|
||||
'edit' entry.";
|
||||
}
|
||||
|
||||
leaf target {
|
||||
type target-resource-offset;
|
||||
mandatory true;
|
||||
description
|
||||
"Identifies the target data node for the edit
|
||||
operation. If the target has the value '/', then
|
||||
the target data node is the target resource.
|
||||
The target node MUST identify a data resource,
|
||||
not the datastore resource.";
|
||||
}
|
||||
|
||||
leaf point {
|
||||
when "(../operation = 'insert' or ../operation = 'move')"
|
||||
+ "and (../where = 'before' or ../where = 'after')" {
|
||||
description
|
||||
"This leaf only applies for 'insert' or 'move'
|
||||
operations, before or after an existing entry.";
|
||||
}
|
||||
type target-resource-offset;
|
||||
description
|
||||
"The absolute URL path for the data node that is being
|
||||
used as the insertion point or move point for the
|
||||
target of this 'edit' entry.";
|
||||
}
|
||||
|
||||
leaf where {
|
||||
when "../operation = 'insert' or ../operation = 'move'" {
|
||||
description
|
||||
"This leaf only applies for 'insert' or 'move'
|
||||
operations.";
|
||||
}
|
||||
type enumeration {
|
||||
enum before {
|
||||
description
|
||||
"Insert or move a data node before the data resource
|
||||
identified by the 'point' parameter.";
|
||||
}
|
||||
enum after {
|
||||
description
|
||||
"Insert or move a data node after the data resource
|
||||
identified by the 'point' parameter.";
|
||||
}
|
||||
|
||||
enum first {
|
||||
description
|
||||
"Insert or move a data node so it becomes ordered
|
||||
as the first entry.";
|
||||
}
|
||||
enum last {
|
||||
description
|
||||
"Insert or move a data node so it becomes ordered
|
||||
as the last entry.";
|
||||
}
|
||||
}
|
||||
default last;
|
||||
description
|
||||
"Identifies where a data resource will be inserted
|
||||
or moved. YANG only allows these operations for
|
||||
list and leaf-list data nodes that are
|
||||
'ordered-by user'.";
|
||||
}
|
||||
|
||||
anydata value {
|
||||
when "../operation = 'create' "
|
||||
+ "or ../operation = 'merge' "
|
||||
+ "or ../operation = 'replace' "
|
||||
+ "or ../operation = 'insert'" {
|
||||
description
|
||||
"The anydata 'value' is only used for 'create',
|
||||
'merge', 'replace', and 'insert' operations.";
|
||||
}
|
||||
description
|
||||
"Value used for this edit operation. The anydata 'value'
|
||||
contains the target resource associated with the
|
||||
'target' leaf.
|
||||
|
||||
For example, suppose the target node is a YANG container
|
||||
named foo:
|
||||
|
||||
container foo {
|
||||
leaf a { type string; }
|
||||
leaf b { type int32; }
|
||||
}
|
||||
|
||||
The 'value' node contains one instance of foo:
|
||||
|
||||
<value>
|
||||
<foo xmlns='example-foo-namespace'>
|
||||
<a>some value</a>
|
||||
<b>42</b>
|
||||
</foo>
|
||||
</value>
|
||||
";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // grouping yang-patch
|
||||
|
||||
grouping yang-patch-status {
|
||||
|
||||
description
|
||||
"A grouping that contains a YANG container representing the
|
||||
syntax and semantics of a YANG Patch Status response
|
||||
message.";
|
||||
|
||||
container yang-patch-status {
|
||||
description
|
||||
"A container representing the response message sent by the
|
||||
server after a YANG Patch edit request message has been
|
||||
processed.";
|
||||
|
||||
leaf patch-id {
|
||||
type string;
|
||||
mandatory true;
|
||||
description
|
||||
"The 'patch-id' value used in the request.";
|
||||
}
|
||||
|
||||
choice global-status {
|
||||
description
|
||||
"Report global errors or complete success.
|
||||
If there is no case selected, then errors
|
||||
are reported in the 'edit-status' container.";
|
||||
|
||||
case global-errors {
|
||||
uses rc:errors;
|
||||
description
|
||||
"This container will be present if global errors that
|
||||
are unrelated to a specific edit occurred.";
|
||||
}
|
||||
leaf ok {
|
||||
type empty;
|
||||
description
|
||||
"This leaf will be present if the request succeeded
|
||||
and there are no errors reported in the 'edit-status'
|
||||
container.";
|
||||
}
|
||||
}
|
||||
|
||||
container edit-status {
|
||||
description
|
||||
"This container will be present if there are
|
||||
edit-specific status responses to report.
|
||||
If all edits succeeded and the 'global-status'
|
||||
returned is 'ok', then a server MAY omit this
|
||||
container.";
|
||||
|
||||
list edit {
|
||||
key edit-id;
|
||||
|
||||
description
|
||||
"Represents a list of status responses,
|
||||
corresponding to edits in the YANG Patch
|
||||
request message. If an 'edit' entry was
|
||||
skipped or not reached by the server,
|
||||
then this list will not contain a corresponding
|
||||
entry for that edit.";
|
||||
|
||||
leaf edit-id {
|
||||
type string;
|
||||
description
|
||||
"Response status is for the 'edit' list entry
|
||||
with this 'edit-id' value.";
|
||||
}
|
||||
|
||||
choice edit-status-choice {
|
||||
description
|
||||
"A choice between different types of status
|
||||
responses for each 'edit' entry.";
|
||||
leaf ok {
|
||||
type empty;
|
||||
description
|
||||
"This 'edit' entry was invoked without any
|
||||
errors detected by the server associated
|
||||
with this edit.";
|
||||
}
|
||||
case errors {
|
||||
uses rc:errors;
|
||||
description
|
||||
"The server detected errors associated with the
|
||||
edit identified by the same 'edit-id' value.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // grouping yang-patch-status
|
||||
|
||||
}
|
||||
EOF1
|
||||
Loading…
Add table
Add a link
Reference in a new issue