This is an advanced topic. Please read about Set-Hook callbacks before you continue this article.
This article will demonstrate and exemplify how to insert or move a specific list or leaf-list entry with help of add_edit_ex() API.
The add_edit_ex() API is only allowed to be used within the Set Hook callbacks as described in the article How do I use a Set Hook callback and add_edit() API?
Please refer to that article for more information on how to use the Set Hook callbacks.
Let us go through simple examples that will illustrate how to utilize the Set Hook callbacks for the specific purpose, such as insert or move an entry. First we need a YANG module. Consider this simplified, but functional, example module:
module example { namespace "http://netconfcentral.org/ns/example"; prefix "ex"; revision 2019-01-01 { description "Initial revision."; } leaf insert-list-check { type string; } leaf insert-leaf-list-check { type string; } leaf move-list-check { type string; } leaf move-leaf-list-check { type string; } list hook-list-test { key name; ordered-by user; leaf name { type string; } leaf a2 { type string; } leaf b2 { type uint32; default 5; } } leaf-list hook-leaf-list-test { ordered-by user; type string; } }
Insert new list nodes
Assume we registered Set Hook callback for the “insert-list-check” leaf element. Thus, whenever the node /ex:insert-list-check is edited, the callback function will be called and additional specific entry can be inserted or moved within the datastore.
In this example, we will insert a new “hook-list-test” list entries when a “insert-list-check” node is modified with specific value equal to. For example, assume we will send the edits with following "insert-list-check" values (trigger, trigger2, trigger3, trigger4). As a result the Set Hook callback function may look as follows:
#define HOOKS_TEST_MOD (const xmlChar *)"example" #define HOOKS_TEST_REV (const xmlChar *)"2019-01-01" /******************************************************************** * FUNCTION create_list_entry * * Make a list entry based on the key * * INPUTS: * res = return status * * RETURNS: * val_value_t of listval entry if no error * else NULL * *********************************************************************/ static val_value_t * create_list_entry (obj_template_t *list_obj, const xmlChar *keyname, const xmlChar *keystr, status_t *res) { /* add all the /container/list nodes */ val_value_t *list_value = val_new_value(); if (!list_value) { *res = ERR_NCX_INVALID_VALUE; return NULL; } val_init_from_template(list_value, list_obj); /* make key leaf entry */ val_value_t *child = agt_make_leaf(list_obj, keyname, keystr, res); if (!child) { val_free_value(list_value); *res = ERR_NCX_INVALID_VALUE; return NULL; } val_add_child(child, list_value); /* generate the internal index Q chain */ *res = val_gen_index_chain(list_obj, list_value); if (*res != NO_ERR) { log_error("\nError: could not generate index chain (%s)", get_error_string(*res)); val_free_value(list_value); return NULL; } return list_value; } /* create_list_entry */ /******************************************************************** * FUNCTION hooks_sethook_edit_insert * * Callback function for server object handler * Used to provide a callback for a specific named object * * INPUTS: * scb == session control block making the request * msg == incoming rpc_msg_t in progress * txcb == transaction control block in progress * editop == edit operation enumeration for the node being edited * newval == container object holding the proposed changes to * apply to the current config, depending on * the editop value. Will not be NULL. * curval == current container values from the <running> * or <candidate> configuration, if any. Could be NULL * for create and other operations. * * RETURNS: * status *********************************************************************/ static status_t hooks_sethook_edit_insert (ses_cb_t *scb, rpc_msg_t *msg, agt_cfg_transaction_t *txcb, op_editop_t editop, val_value_t *newval, val_value_t *curval) { status_t res = NO_ERR; val_value_t *errorval = (curval) ? curval : newval; /* look for a top-node data object */ obj_template_t *targobj = ncx_match_any_object_ex(HOOKS_TEST_MOD, (const xmlChar *)"hook-list-test", FALSE, NCX_MATCH_FIRST, FALSE, &res); if (!targobj) { return ERR_NCX_NOT_FOUND; } val_value_t *editval = NULL; const xmlChar *defpath = NULL; const xmlChar *edit_operation = NULL; const xmlChar *insert_point = NULL; const xmlChar *insert_where = NULL; switch (editop) { case OP_EDITOP_LOAD: break; case OP_EDITOP_MERGE: case OP_EDITOP_REPLACE: case OP_EDITOP_CREATE: /* INSERT KEY2 AFTER KEY1 */ if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger")) { editval = create_list_entry(targobj, (const xmlChar *)"name", (const xmlChar *)"key2", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-list-test[name='key2']"; edit_operation = (const xmlChar *)"insert"; insert_where = (const xmlChar *)"after"; /* assume we already have key1 in the datastore */ insert_point = (const xmlChar *)"/ex:hook-list-test[name='key1']"; /* INSERT KEY3 BEFORE KEY1 */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger2")) { editval = create_list_entry(targobj, (const xmlChar *)"name", (const xmlChar *)"key3", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-list-test[name='key3']"; edit_operation = (const xmlChar *)"insert"; insert_where = (const xmlChar *)"before"; insert_point = (const xmlChar *)"/ex:hook-list-test[name='key1']"; /* INSERT KEY4 LAST */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger3")) { editval = create_list_entry(targobj, (const xmlChar *)"name", (const xmlChar *)"key4", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-list-test[name='key4']"; edit_operation = (const xmlChar *)"insert"; insert_where = (const xmlChar *)"last"; insert_point = NULL; /* INSERT KEY5 FIRST */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger4")) { editval = create_list_entry(targobj, (const xmlChar *)"name", (const xmlChar *)"key5", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-list-test[name='key5']"; edit_operation = (const xmlChar *)"insert"; insert_where = (const xmlChar *)"first"; insert_point = NULL; /* Negative test, missing insert point */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger-fail")) { editval = create_list_entry(targobj, (const xmlChar *)"name", (const xmlChar *)"NEW", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-list-test[name='NEW']"; edit_operation = (const xmlChar *)"insert"; insert_where = (const xmlChar *)"before"; insert_point = (const xmlChar *)"/ex:hook-list-test[name='MISSING']"; /* Negative test, where is missing */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger-fail2")) { editval = create_list_entry(targobj, (const xmlChar *)"name", (const xmlChar *)"NEW", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-list-test[name='NEW']"; edit_operation = (const xmlChar *)"insert"; insert_where = NULL; insert_point = (const xmlChar *)"/ex:hook-list-test[name='key2']"; } else { /* nothing to add or modify. Specific criteria not met */ break; } /* add a new edit on defpath and populate new entry */ if (res == NO_ERR) { res = agt_val_add_edit_ex(scb, msg, txcb, defpath, editval, edit_operation, insert_where, insert_point); } if (editval) { /* clean up the editval */ val_free_value(editval); } /* Final Result should look as follows: * * example:hook-list-test key5 { inserted 'first' by Set-Hook * name key5 * } * example:hook-list-test key3 { inserted 'before' by Set-Hook * name key3 * } * example:hook-list-test key1 { created by edit-config * name key1 * } * example:hook-list-test key2 { inserted 'after' by Set-Hook * name key2 * } * example:hook-list-test key4 { inserted 'last' by Set-Hook * name key4 * } * */ break; case OP_EDITOP_DELETE: break; default: res = SET_ERROR(ERR_INTERNAL_VAL); } if (res != NO_ERR) { agt_record_error(scb, &msg->mhdr, NCX_LAYER_CONTENT, res, NULL, (errorval) ? NCX_NT_VAL : NCX_NT_NONE, errorval, (errorval) ? NCX_NT_VAL : NCX_NT_NONE, errorval); } return res; } /* hooks_sethook_edit_insert */ /******************************************************************** * FUNCTION init_example_test * * Initialize the example module callbacks * *********************************************************************/ static status_t init_example_test (void) { status_t res = NO_ERR; res = agt_cb_hook_register((const xmlChar *)"/ex:insert-list-check", AGT_HOOKFMT_NODE, AGT_HOOK_TYPE_SETHOOK, hooks_sethook_edit_insert); if (res != NO_ERR) { return res; } return NO_ERR; } /* init_example_test */ /******************************************************************** * FUNCTION cleanup_example_test * * Cleanup the example module callbacks * *********************************************************************/ static void cleanup_example_test (void) { agt_cb_hook_unregister((const xmlChar *)"/ex:insert-list-check"); } /* cleanup_example_test */
Now let us go through the example above.
1) Callback register/unregister
As illustrated in the example above we register and unregister Set Hook callback the same way as described in the article How to use Set-Hook callbacks?
/******************************************************************** * FUNCTION init_example_test * * Initialize the example module callbacks * *********************************************************************/ static status_t init_example_test (void) { status_t res = NO_ERR; res = agt_cb_hook_register((const xmlChar *)"/ex:insert-list-check", AGT_HOOKFMT_NODE, AGT_HOOK_TYPE_SETHOOK, hooks_sethook_edit_insert); if (res != NO_ERR) { return res; } return NO_ERR; } /* init_example_test */ /******************************************************************** * FUNCTION cleanup_example_test * * Cleanup the example module callbacks * *********************************************************************/ static void cleanup_example_test (void) { agt_cb_hook_unregister((const xmlChar *)"/ex:insert-list-check"); } /* cleanup_example_test */
2) The most interesting part of the example is the callback itself. In the callback we triggering the add_edit_ex() API based on the "insert-list-check" leaf value. We insert a specific and new list entry based on the "insert-list-check" value.
... /* INSERT KEY2 AFTER KEY1 if "newval" has value of 'trigger' */ if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger")) { ...
In order to create a new list entry and insert it we need to create a val_value_t structure first that represents the "hook-list-test" list node as illustrated in the example:
/******************************************************************** * FUNCTION create_list_entry * * Make a list entry based on the key * * INPUTS: * res = return status * * RETURNS: * val_value_t of listval entry if no error * else NULL * *********************************************************************/ static val_value_t * create_list_entry (obj_template_t *list_obj, const xmlChar *keyname, const xmlChar *keystr, status_t *res) { /* add all the /container/list nodes */ val_value_t *list_value = val_new_value(); if (!list_value) { *res = ERR_NCX_INVALID_VALUE; return NULL; } val_init_from_template(list_value, list_obj); /* make key leaf entry */ val_value_t *child = agt_make_leaf(list_obj, keyname, keystr, res); if (!child) { val_free_value(list_value); *res = ERR_NCX_INVALID_VALUE; return NULL; } val_add_child(child, list_value); /* generate the internal index Q chain */ *res = val_gen_index_chain(list_obj, list_value); if (*res != NO_ERR) { log_error("\nError: could not generate index chain (%s)", get_error_string(*res)); val_free_value(list_value); return NULL; } return list_value; } /* create_list_entry */
3) After we constructed the list node val_value_t tree we are ready to insert this node into the datastore with help of add_edit_ex() API as illustrated in the example:
... /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-list-test[name='key2']"; edit_operation = (const xmlChar *)"insert"; insert_where = (const xmlChar *)"after"; /* assume we already have key1 in the datastore */ insert_point = (const xmlChar *)"/ex:hook-list-test[name='key1']"; } /* add a new edit on defpath and populate new entry */ if (res == NO_ERR) { res = agt_val_add_edit_ex(scb, msg, txcb, defpath, editval, edit_operation, insert_where, insert_point); .....
Where input parameters are:
/******************************************************************** * FUNCTION agt_val_add_edit_ex * * Create a new edit based on edit_value. * if its NULL or invalid the error will be generated. * * Move or insertion OP available. * * Only allowed for Set-Hooks, the rest are ignored * Add_edit is not allowed for default nodes or default NP-contaners * * INPUTS: * txcb == transaction in progress * defpath == XPath path of object instance * edit_value == val_value_t representing newnode in transaction * only needed for create, merge, replace, insert, not delete * * edit_operation == <operation string> * "create" * "delete" * "insert" * "merge" * "move" * "replace" * "remove" * * insert_where == <insert enum string> * "before" * "after" * "first" * "last" * Will be used only if the operations are "move" or "insert". * Ignored otherwise. * * insert_point == is a XPath encoded string like the defpath. Only for * 'before' or 'after' insert_where parameter. The insert_where * must be set to 'before' or 'after' if insert_point specified. * Will be used only if the operations are "move" or "insert". * Ignored otherwise. * E.g: "/test3[string.1='entry2'][uint32.1='2']" * * * RETURNS: * status * *********************************************************************/ status_t agt_val_add_edit_ex (ses_cb_t *scb, rpc_msg_t *msg, agt_cfg_transaction_t *txcb, const xmlChar *defpath, val_value_t *edit_value, const xmlChar *edit_operation, const xmlChar *insert_where, const xmlChar *insert_point)
4) After the callback is called multiple times with different values for "insert-list-check". Assume we send multiple <edit-config> requests with different "insert-list-check" values the data store may look as follows:
/* Final Result should look as follows: * * example:hook-list-test key5 { inserted 'first' by Set-Hook * name key5 * } * example:hook-list-test key3 { inserted 'before' by Set-Hook * name key3 * } * example:hook-list-test key1 { created by edit-config * name key1 * } * example:hook-list-test key2 { inserted 'after' by Set-Hook * name key2 * } * example:hook-list-test key4 { inserted 'last' by Set-Hook * name key4 * } * */
Insert new leaf-list nodes
In this example we will do the same steps but for leaf-list nodes. That's we will try to insert leaf-list entries instead of list entries based on different "insert-leaf-list-check" leaf node values.
The example callback may look as follow, assume we are following the same steps for register and unregister as in the previous example:
#define HOOKS_TEST_MOD (const xmlChar *)"example" #define HOOKS_TEST_REV (const xmlChar *)"2019-01-01" /******************************************************************** * FUNCTION hooks_sethook_edit_insert2 * * Callback function for server object handler * Used to provide a callback for a specific named object * * INPUTS: * scb == session control block making the request * msg == incoming rpc_msg_t in progress * txcb == transaction control block in progress * editop == edit operation enumeration for the node being edited * newval == container object holding the proposed changes to * apply to the current config, depending on * the editop value. Will not be NULL. * curval == current container values from the <running> * or <candidate> configuration, if any. Could be NULL * for create and other operations. * * RETURNS: * status *********************************************************************/ static status_t hooks_sethook_edit_insert2 (ses_cb_t *scb, rpc_msg_t *msg, agt_cfg_transaction_t *txcb, op_editop_t editop, val_value_t *newval, val_value_t *curval) { status_t res = NO_ERR; val_value_t *errorval = (curval) ? curval : newval; /* look for a top-node data object */ obj_template_t *targobj = ncx_match_any_object_ex(HOOKS_TEST_MOD, (const xmlChar *)"hook-leaf-list-test", FALSE, NCX_MATCH_FIRST, FALSE, &res); if (!targobj) { return ERR_NCX_NOT_FOUND; } val_value_t *editval = NULL; const xmlChar *defpath = NULL; const xmlChar *edit_operation = NULL; const xmlChar *insert_point = NULL; const xmlChar *insert_where = NULL; switch (editop) { case OP_EDITOP_LOAD: break; case OP_EDITOP_MERGE: case OP_EDITOP_REPLACE: case OP_EDITOP_CREATE: /* INSERT Leaf-list .=ll2 AFTER .=ll1 */ if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger")) { /* make leaf-list entry. * * The editval is optinal for MOVE operation, * It will be ignored by the add_edit API. The server will * use 'defpath' value as a current target value to move */ editval = agt_make_leaf(targobj, (const xmlChar *)"hook-leaf-list-test", (const xmlChar *)"ll2", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-leaf-list-test[.='ll2']"; edit_operation = (const xmlChar *)"insert"; insert_where = (const xmlChar *)"after"; insert_point = (const xmlChar *)"/ex:hook-leaf-list-test[.='ll1']"; /* INSERT Leaf-list .=ll3 BEFORE .=ll1 */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger2")) { /* make leaf-list entry. * * The editval is optinal for MOVE operation, * It will be ignored by the add_edit API. The server will * use 'defpath' value as a current target value to move */ editval = agt_make_leaf(targobj, (const xmlChar *)"hook-leaf-list-test", (const xmlChar *)"ll3", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-leaf-list-test[.='ll3']"; edit_operation = (const xmlChar *)"insert"; insert_where = (const xmlChar *)"before"; insert_point = (const xmlChar *)"/ex:hook-leaf-list-test[.='ll1']"; /* INSERT Leaf-list .=ll4 LAST */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger3")) { /* make leaf-list entry. * * The editval is optinal for MOVE operation, * It will be ignored by the add_edit API. The server will * use 'defpath' value as a current target value to move */ editval = agt_make_leaf(targobj, (const xmlChar *)"hook-leaf-list-test", (const xmlChar *)"ll4", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-leaf-list-test[.='ll4']"; edit_operation = (const xmlChar *)"insert"; insert_where = (const xmlChar *)"last"; insert_point = NULL; /* INSERT Leaf-list .=ll5 FIRST */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger4")) { /* make leaf-list entry. * * The editval is optinal for MOVE operation, * It will be ignored by the add_edit API. The server will * use 'defpath' value as a current target value to move */ editval = agt_make_leaf(targobj, (const xmlChar *)"hook-leaf-list-test", (const xmlChar *)"ll5", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-leaf-list-test[.='ll5']"; edit_operation = (const xmlChar *)"insert"; insert_where = (const xmlChar *)"first"; insert_point = NULL; /* Negative test, missing insert point */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger-fail")) { /* make leaf-list entry. * * The editval is optinal for MOVE operation, * It will be ignored by the add_edit API. The server will * use 'defpath' value as a current target value to move */ editval = agt_make_leaf(targobj, (const xmlChar *)"hook-leaf-list-test", (const xmlChar *)"NEW", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-leaf-list-test[.='NEW']"; edit_operation = (const xmlChar *)"insert"; insert_where = (const xmlChar *)"before"; insert_point = (const xmlChar *)"/ex:hook-leaf-list-test[.='MISSING']"; /* Negative test, where is missing */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger-fail2")) { /* make leaf-list entry. * * The editval is optinal for MOVE operation, * It will be ignored by the add_edit API. The server will * use 'defpath' value as a current target value to move */ editval = agt_make_leaf(targobj, (const xmlChar *)"hook-leaf-list-test", (const xmlChar *)"NEW", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-leaf-list-test[.='NEW']"; edit_operation = (const xmlChar *)"insert"; insert_where = NULL; insert_point = (const xmlChar *)"/ex:hook-leaf-list-test[.='ll2']"; } else { /* nothing to add or modify. Specific criteria not met */ break; } /* add a new edit on defpath and populate new entry */ if (res == NO_ERR) { res = agt_val_add_edit_ex(scb, msg, txcb, defpath, editval, edit_operation, insert_where, insert_point); } if (editval) { /* clean up the editval */ val_free_value(editval); } /* Final Result should look as follows: * * example:hook-leaf-list-test { * ll5 inserted 'first' by Set-Hook * ll3 inserted 'before' by Set-Hook * ll1 created by edit-config * ll2 inserted 'after' by Set-Hook * ll4 inserted 'last' by Set-Hook * } * */ break; case OP_EDITOP_DELETE: break; default: res = SET_ERROR(ERR_INTERNAL_VAL); } if (res != NO_ERR) { agt_record_error(scb, &msg->mhdr, NCX_LAYER_CONTENT, res, NULL, (errorval) ? NCX_NT_VAL : NCX_NT_NONE, errorval, (errorval) ? NCX_NT_VAL : NCX_NT_NONE, errorval); } return res; } /* hooks_sethook_edit_insert2 */
After the callback is called multiple times with different values for "insert-leaf-list-check". Assume we send multiple <edit-config> requests with different "insert-leaf-list-check" values the data store may look as follows:
/* Final Result should look as follows: * * example:hook-leaf-list-test { * ll5 inserted 'first' by Set-Hook * ll3 inserted 'before' by Set-Hook * ll1 created by edit-config * ll2 inserted 'after' by Set-Hook * ll4 inserted 'last' by Set-Hook * } * */
Move list nodes
In this example we will do the same steps but for the move operation and for list nodes. That's, we will try to move existent in the datastore list entries based on different "move-list-check" leaf node values.
The example callback may look as follow, assume we are following the same steps for register and unregister as in the previous example:
#define HOOKS_TEST_MOD (const xmlChar *)"example" #define HOOKS_TEST_REV (const xmlChar *)"2019-01-01" /******************************************************************** * FUNCTION hooks_sethook_edit_move * * Callback function for server object handler * Used to provide a callback for a specific named object * * * The current datastore entries should look as follows: * * example:hook-list-test key5 { // will be moved last * name key5 * } * example:hook-list-test key3 { // will be moved before 'key4' * name key3 * } * example:hook-list-test key1 { // will be moved first * name key1 * } * example:hook-list-test key2 { // will be moved after 'key3' * name key2 * } * example:hook-list-test key4 { * name key4 * } * * * INPUTS: * scb == session control block making the request * msg == incoming rpc_msg_t in progress * txcb == transaction control block in progress * editop == edit operation enumeration for the node being edited * newval == container object holding the proposed changes to * apply to the current config, depending on * the editop value. Will not be NULL. * curval == current container values from the <running> * or <candidate> configuration, if any. Could be NULL * for create and other operations. * * RETURNS: * status *********************************************************************/ static status_t hooks_sethook_edit_move (ses_cb_t *scb, rpc_msg_t *msg, agt_cfg_transaction_t *txcb, op_editop_t editop, val_value_t *newval, val_value_t *curval) { status_t res = NO_ERR; val_value_t *errorval = (curval) ? curval : newval; /* look for a top-node data object */ obj_template_t *targobj = ncx_match_any_object_ex(HOOKS_TEST_MOD, (const xmlChar *)"hook-list-test", FALSE, NCX_MATCH_FIRST, FALSE, &res); if (!targobj) { return ERR_NCX_NOT_FOUND; } val_value_t *editval = NULL; const xmlChar *defpath = NULL; const xmlChar *edit_operation = NULL; const xmlChar *insert_point = NULL; const xmlChar *insert_where = NULL; switch (editop) { case OP_EDITOP_LOAD: break; case OP_EDITOP_MERGE: case OP_EDITOP_REPLACE: case OP_EDITOP_CREATE: /* MOVE KEY2 AFTER KEY3 */ if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger")) { /* make list entry. * * The editval is optinal for MOVE operation, * It will be ignored by the add_edit API. The server will * use 'defpath' value as a current target value to move */ editval = create_list_entry(targobj, (const xmlChar *)"name", (const xmlChar *)"key2", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-list-test[name='key2']"; edit_operation = (const xmlChar *)"move"; insert_where = (const xmlChar *)"after"; insert_point = (const xmlChar *)"/ex:hook-list-test[name='key3']"; /* MOVE KEY3 BEFORE KEY4 */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger2")) { /* make list entry. * * The editval is optinal for MOVE operation, * It will be ignored by the add_edit API. The server will * use 'defpath' value as a current target value to move */ editval = create_list_entry(targobj, (const xmlChar *)"name", (const xmlChar *)"key3", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-list-test[name='key3']"; edit_operation = (const xmlChar *)"move"; insert_where = (const xmlChar *)"before"; insert_point = (const xmlChar *)"/ex:hook-list-test[name='key4']"; /* MOVE KEY5 LAST */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger3")) { /* make list entry. * * The editval is optinal for MOVE operation, * It will be ignored by the add_edit API. The server will * use 'defpath' value as a current target value to move */ editval = create_list_entry(targobj, (const xmlChar *)"name", (const xmlChar *)"key5", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-list-test[name='key5']"; edit_operation = (const xmlChar *)"move"; insert_where = (const xmlChar *)"last"; insert_point = NULL; /* MOVE KEY1 FIRST */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger4")) { /* make list entry. * * The editval is optinal for MOVE operation, * It will be ignored by the add_edit API. The server will * use 'defpath' value as a current target value to move */ editval = create_list_entry(targobj, (const xmlChar *)"name", (const xmlChar *)"key1", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-list-test[name='key1']"; edit_operation = (const xmlChar *)"move"; insert_where = (const xmlChar *)"first"; insert_point = NULL; /* Negative test, missing move point */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger-fail")) { /* make list entry. * * The editval is optinal for MOVE operation, * It will be ignored by the add_edit API. The server will * use 'defpath' value as a current target value to move */ editval = create_list_entry(targobj, (const xmlChar *)"name", (const xmlChar *)"key1", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-list-test[name='key1']"; edit_operation = (const xmlChar *)"move"; insert_where = (const xmlChar *)"before"; insert_point = (const xmlChar *)"/ex:hook-list-test[name='MISSING']"; /* Negative test, missing target */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger-fail2")) { /* make list entry. * * The editval is optinal for MOVE operation, * It will be ignored by the add_edit API. The server will * use 'defpath' value as a current target value to move */ editval = create_list_entry(targobj, (const xmlChar *)"name", (const xmlChar *)"MISSING", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-list-test[name='MISSING']"; edit_operation = (const xmlChar *)"move"; insert_where = (const xmlChar *)"first"; /* Negative test, where is missing */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger-fail3")) { /* make list entry. * * The editval is optinal for MOVE operation, * It will be ignored by the add_edit API. The server will * use 'defpath' value as a current target value to move */ editval = create_list_entry(targobj, (const xmlChar *)"name", (const xmlChar *)"key1", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-list-test[name='key1']"; edit_operation = (const xmlChar *)"move"; insert_where = NULL; insert_point = (const xmlChar *)"/ex:hook-list-test[name='key2']"; } else { /* nothing to add or modify. Specific criteria not met */ break; } /* add a new edit on defpath and populate new entry */ if (res == NO_ERR) { res = agt_val_add_edit_ex(scb, msg, txcb, defpath, editval, edit_operation, insert_where, insert_point); } if (editval) { /* clean up the editval */ val_free_value(editval); } /* Final Result should look as follows: * * example:hook-list-test key1 { * name key1 * } * example:hook-list-test key2 { * name key2 * } * example:hook-list-test key3 { * name key3 * } * example:hook-list-test key4 { * name key4 * } * example:hook-list-test key5 { * name key5 * } */ break; case OP_EDITOP_DELETE: break; default: res = SET_ERROR(ERR_INTERNAL_VAL); } if (res != NO_ERR) { agt_record_error(scb, &msg->mhdr, NCX_LAYER_CONTENT, res, NULL, (errorval) ? NCX_NT_VAL : NCX_NT_NONE, errorval, (errorval) ? NCX_NT_VAL : NCX_NT_NONE, errorval); } return res; } /* hooks_sethook_edit_move */
After the callback is called multiple times with different values for "move-list-check". Assume we send multiple <edit-config> requests with different "move-list-check" values the data store may look as follows:
/* Final Result should look as follows: * * example:hook-list-test key1 { * name key1 * } * example:hook-list-test key2 { * name key2 * } * example:hook-list-test key3 { * name key3 * } * example:hook-list-test key4 { * name key4 * } * example:hook-list-test key5 { * name key5 * } */
Move leaf-list nodes
In this example we will do the same steps but for the move operation and for leaf-list nodes. That's, we will try to move existent in the datastore leaf-list entries based on different "move-leaf-list-check" leaf node values.
The example callback may look as follow, assume we are following the same steps for register and unregister as in the previous example:
#define HOOKS_TEST_MOD (const xmlChar *)"example" #define HOOKS_TEST_REV (const xmlChar *)"2019-01-01" /******************************************************************** * FUNCTION hooks_sethook_edit_move2 * * Callback function for server object handler * Used to provide a callback for a specific named object * * The current datastore entries should look as follows: * * example:hook-leaf-list-test { * ll5 // will be moved last * ll3 // will be moved before ll4 * ll1 // will be moved first * ll2 // will be moved after ll3 * ll4 * } * * INPUTS: * scb == session control block making the request * msg == incoming rpc_msg_t in progress * txcb == transaction control block in progress * editop == edit operation enumeration for the node being edited * newval == container object holding the proposed changes to * apply to the current config, depending on * the editop value. Will not be NULL. * curval == current container values from the <running> * or <candidate> configuration, if any. Could be NULL * for create and other operations. * * RETURNS: * status *********************************************************************/ static status_t hooks_sethook_edit_move2 (ses_cb_t *scb, rpc_msg_t *msg, agt_cfg_transaction_t *txcb, op_editop_t editop, val_value_t *newval, val_value_t *curval) { status_t res = NO_ERR; val_value_t *errorval = (curval) ? curval : newval; print_callback_info(errorval, AGT_CB_VALIDATE, editop, (const xmlChar *)"SETHOOK-MOVE"); /* look for a top-node data object */ obj_template_t *targobj = ncx_match_any_object_ex(HOOKS_TEST_MOD, (const xmlChar *)"hook-leaf-list-test", FALSE, NCX_MATCH_FIRST, FALSE, &res); if (!targobj) { return ERR_NCX_NOT_FOUND; } val_value_t *editval = NULL; const xmlChar *defpath = NULL; const xmlChar *edit_operation = NULL; const xmlChar *insert_point = NULL; const xmlChar *insert_where = NULL; switch (editop) { case OP_EDITOP_LOAD: break; case OP_EDITOP_MERGE: case OP_EDITOP_REPLACE: case OP_EDITOP_CREATE: /* MOVE leaf-list .='key2' AFTER leaf-list .='key3' */ if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger")) { /* make leaf-list entry. * * The editval is optinal for MOVE operation, * It will be ignored by the add_edit API. The server will * use 'defpath' value as a current target value to move */ editval = agt_make_leaf(targobj, (const xmlChar *)"hook-leaf-list-test", (const xmlChar *)"ll2", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-leaf-list-test[.='ll2']"; edit_operation = (const xmlChar *)"move"; insert_where = (const xmlChar *)"after"; insert_point = (const xmlChar *)"/ex:hook-leaf-list-test[.='ll3']"; /* MOVE leaf-list .='key3' BEFORE leaf-list .='key4' */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger2")) { /* make leaf-list entry. * * The editval is optinal for MOVE operation, * It will be ignored by the add_edit API. The server will * use 'defpath' value as a current target value to move */ editval = agt_make_leaf(targobj, (const xmlChar *)"hook-leaf-list-test", (const xmlChar *)"ll3", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-leaf-list-test[.='ll3']"; edit_operation = (const xmlChar *)"move"; insert_where = (const xmlChar *)"before"; insert_point = (const xmlChar *)"/ex:hook-leaf-list-test[.='ll4']"; /* MOVE leaf-list .='key5' LAST */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger3")) { /* make leaf-list entry. * * The editval is optinal for MOVE operation, * It will be ignored by the add_edit API. The server will * use 'defpath' value as a current target value to move */ editval = agt_make_leaf(targobj, (const xmlChar *)"hook-leaf-list-test", (const xmlChar *)"ll5", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-leaf-list-test[.='ll5']"; edit_operation = (const xmlChar *)"move"; insert_where = (const xmlChar *)"last"; insert_point = NULL; /* MOVE leaf-list .='key1' FIRST */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger4")) { /* make leaf-list entry. * * The editval is optinal for MOVE operation, * It will be ignored by the add_edit API. The server will * use 'defpath' value as a current target value to move */ editval = agt_make_leaf(targobj, (const xmlChar *)"hook-leaf-list-test", (const xmlChar *)"ll1", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-leaf-list-test[.='ll1']"; edit_operation = (const xmlChar *)"move"; insert_where = (const xmlChar *)"first"; insert_point = NULL; /* Negative test, missing move point */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger-fail")) { /* make leaf-list entry. * * The editval is optinal for MOVE operation, * It will be ignored by the add_edit API. The server will * use 'defpath' value as a current target value to move */ editval = agt_make_leaf(targobj, (const xmlChar *)"hook-leaf-list-test", (const xmlChar *)"ll1", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-leaf-list-test[.='ll1']"; edit_operation = (const xmlChar *)"move"; insert_where = (const xmlChar *)"before"; insert_point = (const xmlChar *)"/ex:hook-leaf-list-test[.='MISSING']"; /* Negative test, missing target */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger-fail2")) { /* make leaf-list entry. * * The editval is optinal for MOVE operation, * It will be ignored by the add_edit API. The server will * use 'defpath' value as a current target value to move */ editval = agt_make_leaf(targobj, (const xmlChar *)"hook-leaf-list-test", (const xmlChar *)"MISSING", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-leaf-list-test[.='MISSING']"; edit_operation = (const xmlChar *)"move"; insert_where = (const xmlChar *)"first"; /* Negative test, where is missing */ } else if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger-fail3")) { /* make leaf-list entry. * * The editval is optinal for MOVE operation, * It will be ignored by the add_edit API. The server will * use 'defpath' value as a current target value to move */ editval = agt_make_leaf(targobj, (const xmlChar *)"hook-leaf-list-test", (const xmlChar *)"ll1", &res); if (!editval) { return res; } /* defpath specified target */ defpath = (const xmlChar *)"/ex:hook-leaf-list-test[.='ll1']"; edit_operation = (const xmlChar *)"move"; insert_where = NULL; insert_point = (const xmlChar *)"/ex:hook-leaf-list-test[.='ll2']"; } else { /* nothing to add or modify. Specific criteria not met */ break; } /* add a new edit on defpath and populate new entry */ if (res == NO_ERR) { res = agt_val_add_edit_ex(scb, msg, txcb, defpath, editval, edit_operation, insert_where, insert_point); } if (editval) { /* clean up the editval */ val_free_value(editval); } /* Final Result should look as follows: * * example:hook-leaf-list-test { * "ll1", * "ll2", * "ll3", * "ll4", * "ll5" * } * */ break; case OP_EDITOP_DELETE: break; default: res = SET_ERROR(ERR_INTERNAL_VAL); } if (res != NO_ERR) { agt_record_error(scb, &msg->mhdr, NCX_LAYER_CONTENT, res, NULL, (errorval) ? NCX_NT_VAL : NCX_NT_NONE, errorval, (errorval) ? NCX_NT_VAL : NCX_NT_NONE, errorval); } return res; } /* hooks_sethook_edit_move2 */
After the callback is called multiple times with different values for "move-leaf-list-check". Assume we send multiple <edit-config> requests with different "move-leaf-list-check" values the data store may look as follows:
/* Final Result should look as follows: * * example:hook-leaf-list-test { * "ll1", * "ll2", * "ll3", * "ll4", * "ll5" * } * */
NOTE: The server does not support the "insert" and "move" operation on the nodes that are being modified at the same transaction at the same time. That's if the server already has an undo record (an edit on specific node), then the "insert" or "move" add_edit_ex() API on the same specific node will return an error. The server will only insert a new nodes and move only existent nodes. It will not move a new nodes or a nodes that are being already modified.