AVAILABLE SINCE 20.10-0 RELEASE
A Set Hook is a function that is invoked within the transaction when an object is modified. When the netconfd-pro server has been configured to provide a candidate configuration, Set Hook code will be invoked when changes are done to the <candidate> configuration. If --target=running then the Set Hook will be invoked at the start of the transaction on the running datastore. This callback will be invoked before EDIT callbacks for the same object.
For more derails regarding Set Hook callbacks, refer to the following article:
How do I use SIL-SA version of the Set Hook callback?
Example: Add a New Node
Let us go through simple examples that will illustrate how to utilize the Set Hook callbacks for the specific purposes. First we need a YANG module. Consider this simplified, but functional, example. You can download this YANG module from attachments and run make_sil_sa_dir to auto-generate stub SIL-SA code for this module.
Note, the Set Hook callback is not part of the auto-generated code and you will need to modify this stub SIL-SA code and add registration for your Set Hook callback functions.
SIL-SA code generation command that was used in this example. Please refer to the attached SIL-SA code.
> make_sil_sa_dir silsa-sethook-example --sil-get2
module silsa-sethook-example { namespace "http://netconfcentral.org/ns/silsa-sethook-example"; prefix "sa-sethook-ex"; revision 2020-08-18 { description "Initial revision."; } container interfaces { list interface { key "name"; leaf name { type string; } leaf speed { type enumeration { enum 10m; enum 100m; enum auto; } } leaf hook-node { type uint32; } container state { leaf admin-state { type boolean; } } } leaf status { type string; } } leaf trigger { type string; } }
Assume we registered Set Hook callback for the “trigger” leaf node. Thus, whenever the node /trigger is edited, the Set Hook callback function will be called and additional specific data can be updated or populated with desired values.
In this example, we will generate an extra “interface” list entry with key value equal to “vlan1” when a “trigger” node is getting edited with as specific value equal to “add-edit”. The callback function may look as follows:
/******************************************************************** * FUNCTION sethook_callback * * Callback function for server object handler * Used to provide a callback for a specific named object * * Set Hook: * trigger: edit /trigger * add_edit: * add nodes: populate 1 list entry with name=vlan1 * * path: /interfaces/interface[name=vlan1] * *********************************************************************/ static status_t sethook_callback (ses_cb_t *scb, rpc_msg_t *msg, op_editop_t editop, val_value_t *newval, val_value_t *curval, const xmlChar *transaction_id, boolean isvalidate, boolean isload, boolean isrunning) { status_t res = NO_ERR; val_value_t *errorval = (curval) ? curval : newval; const xmlChar *user = sil_sa_get_username(); const xmlChar *client_addr = sil_sa_get_client_addr(); if (LOGDEBUG2) { log_debug2("\n\n********************************************"); print_callback_info(errorval, AGT_CB_VALIDATE, editop, (const xmlChar *)"SETHOOK"); log_debug2("\ntransaction_id -- %s", transaction_id); log_debug2("\nuser_id -- %s", user); log_debug2("\nclient_addr -- %s", client_addr); log_debug2("\nisvalidate -- %s", isvalidate ? NCX_EL_TRUE : NCX_EL_FALSE); log_debug2("\nisload -- %s", isload ? NCX_EL_TRUE : NCX_EL_FALSE); log_debug2("\nisrunning -- %s", isrunning ? NCX_EL_TRUE : NCX_EL_FALSE); log_debug2("\n********************************************\n\n"); } const xmlChar *defpath = (const xmlChar *)"/sa-sethook-ex:interfaces"; switch (editop) { case OP_EDITOP_LOAD: break; case OP_EDITOP_MERGE: case OP_EDITOP_REPLACE: case OP_EDITOP_CREATE: /* add a new edit if the "/trigger" value is "add-edit" */ if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"add-edit")) { /* find object template of the desired node */ obj_template_t *targobj = ncx_find_object(silsa_sethook_example_mod, (const xmlChar *)"interfaces"); if (!targobj) { return ERR_NCX_INVALID_VALUE; } /* create edit_value container value */ val_value_t *editval = val_new_value(); if (editval == NULL) { return ERR_INTERNAL_MEM; } val_init_from_template(editval, targobj); /* malloc and construct list value */ val_value_t *list_value = create_list_entry(VAL_OBJ(editval), (const xmlChar *)"vlan1", &res); if (!list_value) { val_free_value(editval); return res; } /* add a new list entry */ res = val_child_add(list_value, editval); if (res != NO_ERR) { val_free_value(list_value); } else { /* add a new edit, MERGE on defpath with 1 new list entry */ const xmlChar *edit_operation = (const xmlChar *)"merge"; const xmlChar *insert_point = NULL; const xmlChar *insert_where = NULL; boolean skip_cb = FALSE; res = sil_sa_add_edit(defpath, editval, edit_operation, insert_where, insert_point, skip_cb); } /* clean up the editval */ val_free_value(editval); } 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; } /* sethook_callback */
NOTE: When you are constructing a list node make sure that a key node is getting created first, and then all other children are getting added. Also, make sure that you use val_gen_index_chain API function after you construct the list, so it will be normalized and all required fields will be set accordingly. Refer to the attached SIL-SA code for more information for this callback function.
As a result, whenever some north bound agent edit the /trigger with a specific value, the callback is invoked and additionally creates a new interface /interfaces/interface[name=value]. For more details on how this callback is registered and cleaned up refer to the attached SIL code.
Edit-config:
<edit-config> <target> <candidate/> </target> <default-operation>merge</default-operation> <test-option>set</test-option> <config> <trigger xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="create" xmlns="http://netconfcentral.org/ns/silsa-sethook-example">add-edit</trigger> </config> </edit-config>
The following server output may be seen during the operation described above. The three phase edit-config processing may look as follows. Note that the server sends <server-request> where it specifies the type of the edit as "set hook".
SIL-SA Transaction Start callbacks OK ***** start validate phase on candidate for session 5, transaction 112033 ***** Start container in validate commit for yuma-netconf:config Start leaf in validate commit for silsa-sethook-example:trigger agt_acm: check write <trigger> allowed for user 'tony' agt_acm: PERMIT (access-control off) invoke_simval:validate: silsa-sethook-example:trigger start Start undo for 'silsa-sethook-example:trigger' Starting setup of transaction callbacks Checking for validate user callback for create edit on silsa-sethook-example:trigger Found validate user callback for create:silsa-sethook-example:trigger agt_cfg: add set-hook silcall for silsa-sethook-example:trigger agt_cfg: add silcall for silsa-sethook-example:trigger undo for create op on silsa-sethook-example:trigger Finished setup of transaction callbacks agt_val: Starting remote SIL validate phase ycontrol_msg: sending server-request # 7 for sil-sa ses_msg: send 1.1 buff:988 for s:4 trace_buff: #982 <?xml version="1.0" encoding="UTF-8"?> <ycontrol xmlns:ya="http://yumaworks.com/ns/yumaworks-attrs" xmlns="http://yumaworks.com/ns/yumaworks-ycontrol"> <message-id>7</message-id> <message-type>server-request</message-type> <server-id>server1</server-id> <subsys-id>subsys1</subsys-id> <service-id>sil-sa</service-id> <payload> <sil-sa xmlns="http://yumaworks.com/ns/yumaworks-sil-sa"> <start-transaction> <transaction-id>112033</transaction-id> <user-id>tony</user-id> <client-addr>127.0.0.1</client-addr> <target>candidate</target> <validate>true</validate> <reverse-edit>false</reverse-edit> <load-config>false</load-config> <is-hook-load>false</is-hook-load> <is-hook-validate>false</is-hook-validate> <edit> <id>1</id> <operation>create</operation> <path>/sa-sethook-ex:trigger</path> <hook-type>set-hook</hook-type> <newval> <trigger ya:datapath="/sa-sethook-ex:trigger" xmlns="http: ses_msg: send 1.1 buff:472 for s:4 trace_buff: #462 tconfcentral.org/ns/silsa-sethook-example">add-edit</trigger> </newval> </edit> <edit> <id>2</id> <operation>create</operation> <path>/sa-sethook-ex:trigger</path> <hook-type>none</hook-type> <newval> <trigger ya:datapath="/sa-sethook-ex:trigger" xmlns="http://netconfcentral.org/ns/silsa-sethook-example">add-edit</trigger> </newval> </edit> </start-transaction> </sil-sa> </payload> </ycontrol> agt_sil: Creating SIL-SA transaction timer callback with interval '30' seconds agt_val: enter ncxserver YControl mode
As a result the subsystem will process this request and send an appropriate reply to the server. The subsystem log output may look as follows:
ycontrol_io: read for control session start ses read OK (1000) on session 1 ses_msg: allocate msg 0x55e69705da30 ses_msg: reused in buff 0x55e696f9b0c0 for s 1 ycontrol_io: read for control session start ses read OK (460) on session 1 ses_msg: reused in buff 0x55e696f95c70 for s 1 ycontrol_ses: msg ready for control session yc_parse: Overriding datapath obj 'any' with '/sa-sethook-ex:trigger' yc_parse: Overriding datapath obj 'any' with '/sa-sethook-ex:trigger' ycontrol: dispatch message to service 'sil-sa' sil_sa: set-hook callback on edit 1: 'trigger' for '/sa-sethook-ex:trigger' ******************************************** (trigger) Enter SETHOOK callback ------PHASE:validate;EDITOP:create transaction_id -- 112033 user_id -- user client_addr -- 127.0.0.1 isvalidate -- false isload -- false isrunning -- false ******************************************** sil_sa_hook: Start adding Set Hook edit for merge on /sa-sethook-ex:interfaces sil_sa: done add edit for defpath '/sa-sethook-ex:interfaces' sil_sa: validate callback on edit 2: 'trigger' for '/sa-sethook-ex:trigger' (trigger) Enter EDIT callback ------PHASE:validate;EDITOP:create sil_sa: sending <transaction-response> with added edits ycontrol_msg: sending subsys-response # 4 for sil-sa ses_msg: reused out buff 0x55e696faecd0 for s 1 ses_msg: free msg 0x55e69705da30 for session 1 ses got send request on session 1 ses_msg: send 1.1 buff:967 for s:1 trace_buff: <?xml version="1.0" encoding="UTF-8"?> <ycontrol xmlns="http://yumaworks.com/ns/yumaworks-ycontrol"> <message-id>4</message-id> <message-type>subsys-response</message-type> <server-id>server1</server-id> <subsys-id>subsys1</subsys-id> <service-id>sil-sa</service-id> <payload> <sil-sa xmlns="http://yumaworks.com/ns/yumaworks-sil-sa"> <transaction-response> <transaction-id>112033</transaction-id> <added-edit> <path>/sa-sethook-ex:interfaces</path> <operation>merge</operation> <skip-callback>false</skip-callback> <edit> <interfaces xmlns:ya="http://yumaworks.com/ns/yumaworks-attrs" ya:datapath="/sa-sethook-ex:interfaces" xmlns="http://netconfcentral.org/ns/silsa-sethook-example"> <interface> <name>vlan1</name> <hook-node>1000</hook-node> </interface> </interfaces> </edit> </added-edit> </transaction-response> </sil-sa> </payload> </ycontrol>
As a result of Set Hook invocation the subsystem will add an extra edit and will response to the server to let it to add the edit to the transaction. The server output may look as follows:
agt_ycontrol: Got <ycontrol> message: yctl:ycontrol { message-id 4 message-type subsys-response server-id server1 subsys-id subsys1 service-id sil-sa payload { ysil:sil-sa { transaction-response { transaction-id 112033 added-edit { path /sa-sethook-ex:interfaces operation merge skip-callback false edit { sa-sethook-ex:interfaces { interface vlan1 { name vlan1 hook-node 1000 } } } } } } } } agt_sil: adding transaction-response 4 for subsys subsys1 ses_msg: free msg 0x55ad28134850 for session 4 ycontrol_mode_done for TXID '112033' agt_val: exit ncxserver YControl mode agt_val: start processing added edits for 'subsys1' (ok) Start adding Set Hook edit for merge on /sa-sethook-ex:interfaces agt_val: retrieving '/sa-sethook-ex:interfaces' from candidate datastore eval_expr xpath value result for '/sa-sethook-ex:interfaces' typ: nodeset = node VALHDR sa-sethook-ex:interfaces (1) add_default: checking 'silsa-sethook-example:interfaces' add_node_default: checking 'silsa-sethook-example:interface' add_default: checking 'silsa-sethook-example:interface' add_node_default: checking 'silsa-sethook-example:state' add empty NP container 'silsa-sethook-example:state' add_default: checking 'silsa-sethook-example:state' Start container in validate commit for silsa-sethook-example:interfaces agt_acm: check write <interfaces> allowed for user 'tony' agt_acm: PERMIT (access-control off) invoke_cpxval:validate: silsa-sethook-example:interfaces start Start undo for 'silsa-sethook-example:interfaces' Start list in validate commit for silsa-sethook-example:interface agt_acm: check write <interface> allowed for user 'tony' agt_acm: PERMIT (access-control off) invoke_cpxval:validate: silsa-sethook-example:interface start Start child-undo for 'silsa-sethook-example:interface' Start leaf in validate commit for silsa-sethook-example:name agt_acm: check write <name> allowed for user 'tony' agt_acm: PERMIT (access-control off) invoke_simval:validate: silsa-sethook-example:name start Start leaf in validate commit for silsa-sethook-example:hook-node agt_acm: check write <hook-node> allowed for user 'tony' agt_acm: PERMIT (access-control off) invoke_simval:validate: silsa-sethook-example:hook-node start Start container in validate commit for silsa-sethook-example:state agt_acm: check write <state> allowed for user 'tony' agt_acm: PERMIT (access-control off) invoke_cpxval:validate: silsa-sethook-example:state start Start child-undo for 'silsa-sethook-example:state' agt_val: stop processing added edits for 'subsys1' (ok) Starting setup of Added Edits callbacks Checking for validate user callback for create edit on silsa-sethook-example:interfaces Found validate user callback for create:silsa-sethook-example:interfaces agt_cfg: add silcall for silsa-sethook-example:interfaces Checking for validate user callback for create edit on silsa-sethook-example:interface Found validate user callback for create:silsa-sethook-example:interface agt_cfg: add silcall for silsa-sethook-example:interface Checking for validate user callback for create edit on silsa-sethook-example:name Checking for validate user callback for create edit on silsa-sethook-example:hook-node Found validate user callback for create:silsa-sethook-example:hook-node agt_cfg: add silcall for silsa-sethook-example:hook-node Checking for validate user callback for create edit on silsa-sethook-example:state Found validate user callback for create:silsa-sethook-example:state agt_cfg: add silcall for silsa-sethook-example:state undo for create op on silsa-sethook-example:interfaces Finished setup of Added Edits callbacks ycontrol_msg: sending server-request # 8 for sil-sa ses_msg: send 1.1 buff:988 for s:4 trace_buff: #982 <?xml version="1.0" encoding="UTF-8"?> <ycontrol xmlns:ya="http://yumaworks.com/ns/yumaworks-attrs" xmlns="http://yumaworks.com/ns/yumaworks-ycontrol"> <message-id>8</message-id> <message-type>server-request</message-type> <server-id>server1</server-id> <subsys-id>subsys1</subsys-id> <service-id>sil-sa</service-id> <payload> <sil-sa xmlns="http://yumaworks.com/ns/yumaworks-sil-sa"> <start-transaction> <transaction-id>112033</transaction-id> <user-id>tony</user-id> <client-addr>127.0.0.1</client-addr> <target>candidate</target> <validate>true</validate> <reverse-edit>false</reverse-edit> <load-config>false</load-config> <is-hook-load>false</is-hook-load> <is-hook-validate>false</is-hook-validate> <edit> <id>2</id> <operation>create</operation> <path>/sa-sethook-ex:interfaces</path> <hook-type>none</hook-type> <newval> <interfaces ya:datapath="/sa-sethook-ex:interfaces" xmlns=" ses_msg: send 1.1 buff:988 for s:4 trace_buff: #982 ://netconfcentral.org/ns/silsa-sethook-example"> <interface> <name>vlan1</name> <hook-node>1000</hook-node> <state/> </interface> </interfaces> </newval> </edit> <edit> <id>3</id> <operation>create</operation> <path>/sa-sethook-ex:interfaces/sa-sethook-ex:interface</path> <hook-type>none</hook-type> <newval> <interface ya:datapath="/sa-sethook-ex:interfaces/sa-sethook-ex:interface" xmlns="http://netconfcentral.org/ns/silsa-sethook-example"> <name>vlan1</name> <hook-node>1000</hook-node> <state/> </interface> </newval> <keys> <name ya:datapath="/sa-sethook-ex:interfaces/sa-sethook-ex:interface/sa-sethook-ex:name" xmlns="http://netconfcentral.org/ns/silsa-sethook-example">vlan1</name> </keys> </edit> <edit> <id>4</id> <operation>create</operation> <path>/sa-sethook-ex:interfaces/sa- ses_msg: send 1.1 buff:988 for s:4 trace_buff: #982 ook-ex:interface/sa-sethook-ex:hook-node</path> <hook-type>none</hook-type> <newval> <hook-node ya:datapath="/sa-sethook-ex:interfaces/sa-sethook-ex:interface/sa-sethook-ex:hook-node" xmlns="http://netconfcentral.org/ns/silsa-sethook-example">1000</hook-node> </newval> <keys> <name ya:datapath="/sa-sethook-ex:interfaces/sa-sethook-ex:interface/sa-sethook-ex:name" xmlns="http://netconfcentral.org/ns/silsa-sethook-example">vlan1</name> </keys> </edit> <edit> <id>5</id> <operation>create</operation> <path>/sa-sethook-ex:interfaces/sa-sethook-ex:interface/sa-sethook-ex:state</path> <hook-type>none</hook-type> <newval> <state ya:datapath="/sa-sethook-ex:interfaces/sa-sethook-ex:interface/sa-sethook-ex:state" xmlns="http://netconfcentral.org/ns/silsa-sethook-example"/> </newval> <keys> <name ya:datapath="/sa-sethook-ex:interfaces/s ses_msg: send 1.1 buff:212 for s:4 trace_buff: #202 thook-ex:interface/sa-sethook-ex:name" xmlns="http://netconfcentral.org/ns/silsa-sethook-example">vlan1</name> </keys> </edit> </start-transaction> </sil-sa> </payload> </ycontrol> agt_sil: Creating SIL-SA transaction timer callback with interval '30' seconds agt_val: enter ncxserver YControl mode
The server will add an extra edit generated by the subsystem and will call the subsystem again in order to invoke the callbacks for newly added nodes.
NOTE: this invocation may be skipped by the sil_sa_add_edit() API parameter skip_cb. If it is set to TRUE then the server will not try to invoke any callbacks for newly added node(s).
After the regular edit configuration procedure for this newly added nodes the subsystem should reply with OK message or an ERROR. If the result of this subsystem invocation is successful the server will continue the translation and the output of the server log may look as follows:
agt_ycontrol: Got <ycontrol> message: yctl:ycontrol { message-id 8 message-type subsys-response server-id server1 subsys-id subsys1 service-id sil-sa ok } ses_msg: free msg 0x55ad28134850 for session 4 ycontrol_mode_done for TXID '112033' agt_val: exit ncxserver YControl mode agt_val: Finished remote SIL validate phase ***** start apply phase on candidate for session 5, transaction 112033 ***** apply_write_val: trigger start apply_write_val[5]: apply create on /sa-sethook-ex:trigger Add child 'trigger' to parent 'config' apply_write_val: interfaces start add_default: checking 'silsa-sethook-example:interfaces' add_node_default: checking 'silsa-sethook-example:interface' add_default: checking 'silsa-sethook-example:interface' add_node_default: checking 'silsa-sethook-example:state' add_default: checking 'silsa-sethook-example:state' apply_write_val[5]: apply create on /sa-sethook-ex:interfaces start delete_dead_nodes2 for transaction delete_dead_nodes2: undo for silsa-sethook-example:trigger start delete_dead_nodes for node 'silsa-sethook-example:trigger' start run_external_xpath_tests for 'silsa-sethook-example:trigger' start delete_extern_nodes for 'trigger' delete_dead_nodes2: undo for silsa-sethook-example:interfaces start delete_dead_nodes for node 'silsa-sethook-example:interfaces' start run_external_xpath_tests for 'silsa-sethook-example:interfaces' start delete_extern_nodes for 'interfaces' start run_external_xpath_tests for 'silsa-sethook-example:interface' start delete_extern_nodes for 'interface' start run_external_xpath_tests for 'silsa-sethook-example:name' start run_external_xpath_tests for 'silsa-sethook-example:speed' start run_external_xpath_tests for 'silsa-sethook-example:hook-node' start run_external_xpath_tests for 'silsa-sethook-example:state' start delete_extern_nodes for 'state' start run_external_xpath_tests for 'silsa-sethook-example:admin-state' start run_external_xpath_tests for 'silsa-sethook-example:status' ***** start commit phase on candidate for session 5, transaction 112033 ***** Start full commit of transaction 112033: 2 edits on candidate config edit-transaction 112033: on session 5 by [email protected] time: 2020-08-18T23:54:41Z message-id: 2 trace-id: -- datastore: candidate operation: create target: /sa-sethook-ex:trigger comment: none edit-transaction 112033: on session 5 by [email protected] time: 2020-08-18T23:54:41Z message-id: 2 trace-id: -- datastore: candidate operation: create target: /sa-sethook-ex:interfaces comment: none Complete commit OK of transaction 112033 on candidate database
To ensure that the data added by subsystem was successfully generated an application can retrieve configurations. The server should reply with:
<data> <interfaces xmlns="http://netconfcentral.org/ns/silsa-sethook-example"> <interface> <name>vlan1</name> <hook-node>1000</hook-node> </interface> </interfaces> <trigger xmlns="http://netconfcentral.org/ns/silsa-sethook-example">add-edit</trigger> </data>