- 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] 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
|
||||||
|
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
|
||||||
* @note restconf PUT is mapped to edit-config replace.
|
* @note restconf PUT is mapped to edit-config replace.
|
||||||
* @see RFC8040 Sec 4.5 PUT
|
* @see RFC8040 Sec 4.5 PUT
|
||||||
* @see api_data_post
|
* @see api_data_post
|
||||||
|
|
@ -645,6 +645,7 @@ api_data_put(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
|
||||||
|
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
|
||||||
* Netconf: <edit-config> (nc:operation="merge")
|
* Netconf: <edit-config> (nc:operation="merge")
|
||||||
* See RFC8040 Sec 4.6.1
|
* See RFC8040 Sec 4.6.1
|
||||||
* Plain patch can be used to create or update, but not delete, a child
|
* 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:
|
case YANG_PATCH_XML:
|
||||||
#ifdef YANG_PATCH
|
#ifdef YANG_PATCH
|
||||||
ret = api_data_yang_patch(h, req, api_path0, pcvec, pi, qvec, data, pretty,
|
ret = api_data_yang_patch(h, req, api_path0, pcvec, pi, qvec, data, pretty,
|
||||||
media_out, ds);
|
media_in, media_out, ds);
|
||||||
#else
|
#else
|
||||||
ret = restconf_notimplemented(h, req, pretty, media_out);
|
ret = restconf_notimplemented(h, req, pretty, media_out);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -318,6 +318,7 @@ api_data_get2(clicon_handle h,
|
||||||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||||
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||||
* @param[in] media_out Output media
|
* @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
|
* The HEAD method is sent by the client to retrieve just the header fields
|
||||||
* that would be returned for the comparable GET method, without the
|
* that would be returned for the comparable GET method, without the
|
||||||
|
|
@ -348,11 +349,10 @@ api_data_head(clicon_handle h,
|
||||||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||||
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||||
* @param[in] media_out Output media
|
* @param[in] media_out Output media
|
||||||
|
* @param[in] ds RFC8527 datastore
|
||||||
* @code
|
* @code
|
||||||
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
|
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
|
||||||
* @endcode
|
* @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
|
* Request may contain
|
||||||
* Accept: application/yang.data+json,application/yang.data+xml
|
* Accept: application/yang.data+json,application/yang.data+xml
|
||||||
* Response contains one of:
|
* Response contains one of:
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
Copyright (C) 2009-2019 Olof Hagsand
|
Copyright (C) 2009-2019 Olof Hagsand
|
||||||
Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate)
|
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.
|
This file is part of CLIXON.
|
||||||
|
|
||||||
|
|
@ -32,7 +33,7 @@
|
||||||
|
|
||||||
***** END LICENSE BLOCK *****
|
***** END LICENSE BLOCK *****
|
||||||
|
|
||||||
* Restconf YANG PATCH implementation
|
* Restconf YANG PATCH implementation (RFC8072)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -68,42 +69,42 @@
|
||||||
|
|
||||||
#ifdef YANG_PATCH
|
#ifdef YANG_PATCH
|
||||||
|
|
||||||
/*! Return a value within XML tags
|
enum yang_patch_op{
|
||||||
* @param [in] nsc namespace context
|
YANG_PATCH_OP_CREATE,
|
||||||
* @param [in] xn cxobj containing XML with the current edit
|
YANG_PATCH_OP_DELETE,
|
||||||
* @param [in] val cbuf to which the value will be written
|
YANG_PATCH_OP_INSERT,
|
||||||
* @param [in] key string containing the tag
|
YANG_PATCH_OP_MERGE,
|
||||||
* @retval 0 success
|
YANG_PATCH_OP_MOVE,
|
||||||
* @retval <0 failure
|
YANG_PATCH_OP_REPLACE,
|
||||||
*/
|
YANG_PATCH_OP_REMOVE
|
||||||
static int
|
};
|
||||||
yang_patch_get_xval(cvec *nsc,
|
typedef enum yang_patch_op yang_patch_op_t;
|
||||||
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)
|
/* Yang patch operations according to RFC 8072
|
||||||
return ret;
|
*/
|
||||||
if (veclen == 1){ //veclen should always be 1
|
static const map_str2int yang_patch_op_map[] = {
|
||||||
xn_tmp = vec[0];
|
{"create", YANG_PATCH_OP_CREATE},
|
||||||
tmp_val = xml_body(xn_tmp);
|
{"delete", YANG_PATCH_OP_DELETE},
|
||||||
cbuf_append_str(val, tmp_val);
|
{"insert", YANG_PATCH_OP_INSERT},
|
||||||
}
|
{"merge", YANG_PATCH_OP_MERGE},
|
||||||
return 0;
|
{"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
|
/*! Add square brackets after the surrounding curly brackets in JSON
|
||||||
* Needed, in order to modify the result of xml2json_cbuf() to be valid input
|
* Needed, in order to modify the result of xml2json_cbuf() to be valid input
|
||||||
* to api_data_post() and api_data_write()
|
* to api_data_post() and api_data_write()
|
||||||
* @param[in] x_simple_patch a cxobj to pass to xml2json_cbuf()
|
* @param[in] x_simple_patch a cxobj to pass to xml2json_cbuf()
|
||||||
* @retval new cbuf with the modified json
|
* @retva cbuf With the modified json
|
||||||
* @retval NULL Error
|
* @retva NULL Error
|
||||||
*/
|
*/
|
||||||
static cbuf*
|
static cbuf*
|
||||||
yang_patch_xml2json_modified_cbuf(cxobj *x_simple_patch)
|
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/"
|
* or "/interface_list=mylist/interface=eth2" becomes "/interface_list=mylist/"
|
||||||
*
|
*
|
||||||
* @param[in] val value to strip
|
* @param[in] val value to strip
|
||||||
* @retval new cbuf with the stripped string
|
* @retval cbuf with the stripped string
|
||||||
* @retval NULL error
|
* @retval NULL error
|
||||||
*/
|
*/
|
||||||
static cbuf*
|
static cbuf*
|
||||||
yang_patch_strip_after_last_slash(cbuf* val)
|
yang_patch_strip_after_last_slash(char* val)
|
||||||
{
|
{
|
||||||
cbuf *cb;
|
cbuf *cb;
|
||||||
cbuf *val_tmp;
|
cbuf *val_tmp;
|
||||||
|
|
@ -165,7 +166,7 @@ yang_patch_strip_after_last_slash(cbuf* val)
|
||||||
|
|
||||||
cb = cbuf_new();
|
cb = cbuf_new();
|
||||||
val_tmp = 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);
|
idx = cbuf_len(val_tmp);
|
||||||
for (int l = cbuf_len(val_tmp) - 1; l>= 0; l--) {
|
for (int l = cbuf_len(val_tmp) - 1; l>= 0; l--) {
|
||||||
if (cbuf_get(val_tmp)[l] == '/') {
|
if (cbuf_get(val_tmp)[l] == '/') {
|
||||||
|
|
@ -205,48 +206,55 @@ yang_patch_do_replace (clicon_handle h,
|
||||||
restconf_media media_out,
|
restconf_media media_out,
|
||||||
ietf_ds_t ds,
|
ietf_ds_t ds,
|
||||||
cbuf *simple_patch_request_uri,
|
cbuf *simple_patch_request_uri,
|
||||||
cbuf *target_val,
|
char *target_val,
|
||||||
int value_vec_len,
|
int value_vec_len,
|
||||||
cxobj **value_vec,
|
cxobj **value_vec,
|
||||||
cxobj *x_simple_patch
|
cxobj *x_simple_patch
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
int retval = -1;
|
||||||
cxobj *value_vec_tmp = NULL;
|
cxobj *value_vec_tmp = NULL;
|
||||||
cbuf *delete_req_uri = NULL;
|
cbuf *delete_req_uri = NULL;
|
||||||
int ret;
|
|
||||||
cbuf *post_req_uri = NULL;
|
cbuf *post_req_uri = NULL;
|
||||||
cbuf *json_simple_patch = NULL;
|
cbuf *json_simple_patch = NULL;
|
||||||
|
|
||||||
delete_req_uri = cbuf_new();
|
if ((delete_req_uri = cbuf_new()) == NULL){
|
||||||
if (delete_req_uri == NULL)
|
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||||
return 1;
|
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"
|
// 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)
|
if (cbuf_append_str(delete_req_uri, cbuf_get(simple_patch_request_uri)) < 0){
|
||||||
return 1;
|
clicon_err(OE_UNIX, errno, "cbuf_append_str");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
// Add the target to delete_req_uri,
|
// Add the target to delete_req_uri,
|
||||||
// so it's something like "/restconf/data/ietf-interfaces:interfaces/interface=eth2"
|
// so it's something like "/restconf/data/ietf-interfaces:interfaces/interface=eth2"
|
||||||
if (cbuf_append_str(delete_req_uri, cbuf_get(target_val)) < 0)
|
if (cbuf_append_str(delete_req_uri, target_val) < 0){
|
||||||
return 1;
|
clicon_err(OE_UNIX, errno, "cbuf_append_str");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
// Delete the object with the old values
|
// Delete the object with the old values
|
||||||
ret = api_data_delete(h, req, cbuf_get(delete_req_uri), pi, pretty, YANG_DATA_JSON, ds );
|
if (api_data_delete(h, req, cbuf_get(delete_req_uri), pi, pretty, YANG_DATA_JSON, ds) < 0)
|
||||||
cbuf_free(delete_req_uri);
|
goto done;
|
||||||
if (ret != 0)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
// Now set up for the post request.
|
// Now set up for the post request.
|
||||||
// Strip /... from end of target val
|
// Strip /... from end of target val
|
||||||
// so that e.g. "/interface=eth2" becomes "/"
|
// so that e.g. "/interface=eth2" becomes "/"
|
||||||
// or "/interface_list=mylist/interface=eth2" becomes "/interface_list=mylist/"
|
// 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"
|
// Make post_req_uri something like "/restconf/data/ietf-interfaces:interfaces"
|
||||||
if (cbuf_append_str(simple_patch_request_uri, cbuf_get(post_req_uri)))
|
if (cbuf_append_str(simple_patch_request_uri, cbuf_get(post_req_uri)) < 0){
|
||||||
return 1;
|
clicon_err(OE_UNIX, errno, "cbuf_append_str");
|
||||||
cbuf_free(post_req_uri);
|
goto done;
|
||||||
|
}
|
||||||
// Now insert the new values into the data
|
// Now insert the new values into the data
|
||||||
// (which will include the key value and all other mandatory values)
|
// (which will include the key value and all other mandatory values)
|
||||||
for (int k = 0; k < value_vec_len; k++) {
|
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
|
// 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);
|
xml2json_cbuf(json_simple_patch, x_simple_patch, 1);
|
||||||
|
|
||||||
// Send the POST request
|
// 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);
|
cbuf_free(json_simple_patch);
|
||||||
|
if (value_vec_tmp)
|
||||||
xml_free(value_vec_tmp);
|
xml_free(value_vec_tmp);
|
||||||
return ret;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! YANG PATCH create method
|
/*! YANG PATCH create method
|
||||||
|
|
@ -299,29 +312,29 @@ yang_patch_do_create (clicon_handle h,
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
cxobj *value_vec_tmp = NULL;
|
cxobj *value_vec_tmp = NULL;
|
||||||
cbuf *cb = 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
|
// Send the POST request
|
||||||
if ((cb = cbuf_new()) == NULL){
|
if ((cb = cbuf_new()) == NULL){
|
||||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||||
goto done;
|
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)
|
if (xml2json_cbuf(cb, x_simple_patch, 1) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
json_simple_patch = cbuf_get(cb);
|
|
||||||
if (api_data_post(h, req, cbuf_get(simple_patch_request_uri),
|
if (api_data_post(h, req, cbuf_get(simple_patch_request_uri),
|
||||||
pi, qvec,
|
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;
|
goto done;
|
||||||
xml_free(value_vec_tmp);
|
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
if (cb)
|
||||||
|
cbuf_free(cb);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -351,17 +364,26 @@ yang_patch_do_insert (clicon_handle h,
|
||||||
int value_vec_len,
|
int value_vec_len,
|
||||||
cxobj **value_vec,
|
cxobj **value_vec,
|
||||||
cxobj *x_simple_patch,
|
cxobj *x_simple_patch,
|
||||||
cbuf *where_val,
|
char *where_val,
|
||||||
char *api_path,
|
char *api_path,
|
||||||
cbuf *point_val
|
char *point_val
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
int retval = -1;
|
||||||
cxobj *value_vec_tmp = NULL;
|
cxobj *value_vec_tmp = NULL;
|
||||||
cbuf *json_simple_patch;
|
cbuf *json_simple_patch;
|
||||||
cg_var *cv;
|
cg_var *cv;
|
||||||
cbuf *point_str = NULL;
|
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
|
// Loop through the XML, and get each value
|
||||||
for (int k = 0; k < value_vec_len; k++) {
|
for (int k = 0; k < value_vec_len; k++) {
|
||||||
if (value_vec[k] != NULL) {
|
if (value_vec[k] != NULL) {
|
||||||
|
|
@ -369,38 +391,39 @@ yang_patch_do_insert (clicon_handle h,
|
||||||
xml_addsub(x_simple_patch, value_vec_tmp);
|
xml_addsub(x_simple_patch, value_vec_tmp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch);
|
if ((json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch)) == NULL)
|
||||||
if (json_simple_patch == NULL)
|
goto done;
|
||||||
return 1;
|
|
||||||
|
|
||||||
// Set the insert attributes
|
// 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){
|
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_name_set(cv, "insert");
|
||||||
cv_string_set(cv, cbuf_get(where_val));
|
if (where_val)
|
||||||
point_str = cbuf_new();
|
cv_string_set(cv, where_val);
|
||||||
if (point_str == NULL)
|
|
||||||
return 1;
|
|
||||||
cbuf_append_str(point_str, api_path);
|
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){
|
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_name_set(cv, "point");
|
||||||
cv_string_set(cv, cbuf_get(point_str));
|
cv_string_set(cv, cbuf_get(point_str));
|
||||||
|
|
||||||
// Send the POST request
|
// 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 );
|
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)
|
||||||
xml_free(value_vec_tmp);
|
goto done;
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
if (qvec_tmp)
|
||||||
|
cvec_free(qvec_tmp);
|
||||||
|
if (point_str)
|
||||||
cbuf_free(point_str);
|
cbuf_free(point_str);
|
||||||
|
if (json_simple_patch)
|
||||||
cbuf_free(json_simple_patch);
|
cbuf_free(json_simple_patch);
|
||||||
return ret;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! YANG PATCH merge method
|
/*! YANG PATCH merge method
|
||||||
|
|
@ -435,11 +458,15 @@ yang_patch_do_merge(clicon_handle h,
|
||||||
cxobj *key_xn
|
cxobj *key_xn
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
int ret = -1;
|
int retval = -1;
|
||||||
cxobj *value_vec_tmp = NULL;
|
cxobj *value_vec_tmp = NULL;
|
||||||
cbuf *cb = NULL;
|
cbuf *cb = NULL;
|
||||||
cbuf *json_simple_patch = NULL;
|
cbuf *json_simple_patch = NULL;
|
||||||
|
|
||||||
|
if ((cb = cbuf_new()) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
if (key_xn != NULL)
|
if (key_xn != NULL)
|
||||||
xml_addsub(x_simple_patch, key_xn);
|
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]);
|
value_vec_tmp = xml_dup(value_vec[k]);
|
||||||
xml_addsub(x_simple_patch, value_vec_tmp);
|
xml_addsub(x_simple_patch, value_vec_tmp);
|
||||||
}
|
}
|
||||||
cb = cbuf_new();
|
cbuf_reset(cb); /* reuse cb */
|
||||||
xml2json_cbuf(cb, x_simple_patch, 1);
|
xml2json_cbuf(cb, x_simple_patch, 1);
|
||||||
|
|
||||||
json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch);
|
if ((json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch)) == NULL)
|
||||||
if (json_simple_patch == NULL)
|
goto done;
|
||||||
return 1;
|
|
||||||
xml_free(value_vec_tmp);
|
|
||||||
// Send the simple patch request
|
// 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 );
|
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)
|
||||||
cbuf_free(cb);
|
goto done;
|
||||||
|
if (json_simple_patch){
|
||||||
cbuf_free(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
|
/*! 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] qvec Vector of query string (QUERY_STRING)
|
||||||
* @param[in] data Stream input data
|
* @param[in] data Stream input data
|
||||||
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
* @param[in] pretty Set to 1 for pretty-printed xml/json output
|
||||||
|
* @param[in] media_in Input media
|
||||||
* @param[in] media_out Output 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")
|
* Netconf: <edit-config> (nc:operation="merge")
|
||||||
* See RFC8072
|
* @see RFC8072
|
||||||
* YANG patch can be used to "create", "delete", "insert", "merge", "move", "replace", and/or
|
* YANG patch can be used to "create", "delete", "insert", "merge", "move", "replace", and/or
|
||||||
"remove" a resource within the target resource.
|
"remove" a resource within the target resource.
|
||||||
* Currently "move" not supported
|
* Currently "move" not supported
|
||||||
|
|
@ -489,31 +752,18 @@ api_data_yang_patch(clicon_handle h,
|
||||||
cvec *qvec,
|
cvec *qvec,
|
||||||
char *data,
|
char *data,
|
||||||
int pretty,
|
int pretty,
|
||||||
|
restconf_media media_in,
|
||||||
restconf_media media_out,
|
restconf_media media_out,
|
||||||
ietf_ds_t ds)
|
ietf_ds_t ds)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
int i;
|
int i;
|
||||||
cxobj *xdata0 = NULL; /* Original -d data struct (including top symbol) */
|
cxobj *xpatch = NULL;
|
||||||
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;
|
yang_stmt *yspec;
|
||||||
char *api_path;
|
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 */
|
cxobj *xerr = NULL; /* malloced must be freed */
|
||||||
int ret;
|
int ret;
|
||||||
cvec *nsc = NULL;
|
char *uripath0 = 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;
|
size_t veclen;
|
||||||
cxobj **vec = NULL;
|
cxobj **vec = NULL;
|
||||||
|
|
||||||
|
|
@ -526,31 +776,27 @@ api_data_yang_patch(clicon_handle h,
|
||||||
/* strip /... from start */
|
/* strip /... from start */
|
||||||
for (i=0; i<pi; i++)
|
for (i=0; i<pi; i++)
|
||||||
api_path = index(api_path+1, '/');
|
api_path = index(api_path+1, '/');
|
||||||
/* Translate yang-patch path to xpath: xpath (cbpath) and namespace context (nsc) */
|
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)
|
||||||
if ((ret = api_path2xpath(yang_patch_path, yspec, &xpath, &nsc, &xerr)) < 0)
|
|
||||||
goto done;
|
goto done;
|
||||||
if (ret == 0){ /* validation failed */
|
|
||||||
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
|
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
/* Create config top-of-tree */
|
switch (media_in){
|
||||||
if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
|
case YANG_PATCH_XML:
|
||||||
goto done;
|
ret = clixon_xml_parse_string(data, YB_MODULE, yspec, &xpatch, &xerr);
|
||||||
|
break;
|
||||||
/* Translate yang-patch path to xml in the form of xtop/xbot */
|
case YANG_PATCH_JSON: /* RFC 8072 patch */
|
||||||
xbot = xtop;
|
ret = clixon_json_parse_string(data, YB_MODULE, yspec, &xpatch, &xerr);
|
||||||
if ((ret = api_path2xml(yang_patch_path, yspec, xtop, YC_DATANODE, 1, &xbot, &ybot, &xerr)) < 0)
|
break;
|
||||||
goto done;
|
default:
|
||||||
if (ret == 0){ /* validation failed */
|
restconf_unsupported_media(h, req, pretty, media_out);
|
||||||
if (api_return_err(h, req, xerr, pretty, media_out, 0) < 0)
|
|
||||||
goto done;
|
|
||||||
goto ok;
|
goto ok;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
/* Common error handling for json/xml parsing above */
|
||||||
yb = YB_MODULE;
|
if (ret < 0){
|
||||||
if ((ret = clixon_json_parse_string(data, yb, yspec, &xbot, &xerr)) < 0){
|
|
||||||
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
|
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
|
* 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)
|
if (netconf_malformed_message_xml(&xerr, "The message-body MUST contain exactly one instance of the expected data resource") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
|
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
/* Save original uripath (will be changed by sub-calls) */
|
||||||
while ((x = xml_child_each(xbot, x, CX_ELMNT)) != NULL){
|
if ((uripath0 = restconf_uripath(h)) == 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;
|
goto done;
|
||||||
} else {
|
/* Find all edit operations and loop over them
|
||||||
cbuf_append_str(path_orig_1, restconf_uripath(h));
|
*/
|
||||||
}
|
if (xpath_vec(xpatch, NULL, "yang-patch/edit", &vec, &veclen) < 0)
|
||||||
|
|
||||||
// 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;
|
goto done;
|
||||||
}
|
for (i = 0; i < veclen; i++) {
|
||||||
// Get operation
|
if (yang_patch_do_edit(h, req, pcvec, pi, qvec, pretty, media_out, ds,
|
||||||
cbuf *op_val = cbuf_new();
|
yspec,
|
||||||
ret = yang_patch_get_xval(nsc, xn, op_val, "operation");
|
vec[i],
|
||||||
if (ret < 0) {
|
uripath0, api_path) < 0)
|
||||||
goto done;
|
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:
|
ok:
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
cbuf_free(path_orig_1);
|
if (uripath0)
|
||||||
|
free(uripath0);
|
||||||
if (vec)
|
if (vec)
|
||||||
free(vec);
|
free(vec);
|
||||||
if (xpath)
|
|
||||||
free(xpath);
|
|
||||||
if (nsc)
|
|
||||||
xml_nsctx_free(nsc);
|
|
||||||
if (xret)
|
|
||||||
xml_free(xret);
|
|
||||||
if (xerr)
|
if (xerr)
|
||||||
xml_free(xerr);
|
xml_free(xerr);
|
||||||
if (xretcom)
|
if (xpatch)
|
||||||
xml_free(xretcom);
|
xml_free(xpatch);
|
||||||
if (xretdis)
|
|
||||||
xml_free(xretdis);
|
|
||||||
if (xtop)
|
|
||||||
xml_free(xtop);
|
|
||||||
if (xdata0)
|
|
||||||
xml_free(xdata0);
|
|
||||||
if (cbx)
|
|
||||||
cbuf_free(cbx);
|
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -774,6 +857,7 @@ api_data_yang_patch(clicon_handle h,
|
||||||
cvec *qvec,
|
cvec *qvec,
|
||||||
char *data,
|
char *data,
|
||||||
int pretty,
|
int pretty,
|
||||||
|
restconf_media media_in,
|
||||||
restconf_media media_out,
|
restconf_media media_out,
|
||||||
ietf_ds_t ds)
|
ietf_ds_t ds)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
Copyright (C) 2009-2019 Olof Hagsand
|
Copyright (C) 2009-2019 Olof Hagsand
|
||||||
Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate)
|
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.
|
This file is part of CLIXON.
|
||||||
|
|
||||||
|
|
@ -32,7 +33,7 @@
|
||||||
|
|
||||||
***** END LICENSE BLOCK *****
|
***** 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,
|
int api_data_yang_patch(clicon_handle h, void *req, char *api_path0,
|
||||||
cvec *pcvec, int pi,
|
cvec *pcvec, int pi,
|
||||||
cvec *qvec, char *data,
|
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_ */
|
#endif /* _RESTCONF_METHODS_PATCH_H_ */
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,7 @@ 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
|
||||||
|
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
|
||||||
* @retval 0 OK
|
* @retval 0 OK
|
||||||
* @retval -1 Error
|
* @retval -1 Error
|
||||||
* restconf POST is mapped to edit-config create.
|
* restconf POST is mapped to edit-config create.
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ APPNAME=example
|
||||||
cfg=$dir/conf.xml
|
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
|
|
||||||
|
|
||||||
# Define default restconfig config: RESTCONFIG
|
# Define default restconfig config: RESTCONFIG
|
||||||
RESTCONFIG=$(restconf_config user false)
|
RESTCONFIG=$(restconf_config user false)
|
||||||
|
|
@ -111,9 +110,6 @@ cat <<EOF > $dir/example-system.yang
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Yang Patch spec (fyangpatch must be set)
|
|
||||||
. ./yang-patch.sh
|
|
||||||
|
|
||||||
# Common Jukebox spec (fjukebox must be set)
|
# Common Jukebox spec (fjukebox must be set)
|
||||||
. ./jukebox.sh
|
. ./jukebox.sh
|
||||||
|
|
||||||
|
|
@ -15,7 +15,6 @@ APPNAME=example
|
||||||
cfg=$dir/conf.xml
|
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
|
|
||||||
finterfaces=$dir/ietf-interfaces.yang
|
finterfaces=$dir/ietf-interfaces.yang
|
||||||
fexample=$dir/clixon-example.yang
|
fexample=$dir/clixon-example.yang
|
||||||
|
|
||||||
|
|
@ -105,9 +104,6 @@ cat <<EOF > $dir/example-system.yang
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Yang Patch spec (fyangpatch must be set)
|
|
||||||
. ./yang-patch.sh
|
|
||||||
|
|
||||||
# Interfaces spec (finterfaces must be set)
|
# Interfaces spec (finterfaces must be set)
|
||||||
. ./interfaces.sh
|
. ./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