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 EDIT1 or EDIT2 callbacks for the same object.

For more derails, refer to the following article:

How do I use a Set Hook callback?



Example 1: Modify a leaf edit value in progress


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 attached YANG module, simplified, but functional, example. You can download this YANG module from attachments and run make_sil_dir_pro to auto-generate stub SIL 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 code and add registration for your Set Hook callbacks and the callbacks functions.


SIL code Generation command that was used in this example. Please refer to the attached SIL code.


> make_sil_dir_pro sethook-example_modify --sil-get2 sil-edit2


module sethook-example_modify {
  namespace "http://netconfcentral.org/ns/sethook-example";
  prefix "sethook-ex";

  revision 2020-03-20 {
    description "Initial revision.";
  }

  container interfaces {                     // edit2 cb
    list interface {                         // edit2 cb
      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 {                              // edit2 cb + Set Hook (Node)
    type string;
  }
}



Consider the data model as specified above, now we will register callback for the top level /trigger leaf. In this example, we will modify its value on the fly when the leaf node is getting edited (created).


    /* Register an object specific Set Hook callback */ 
    res = agt_cb_hook_register((const xmlChar*)"/sethook-ex:trigger", 
                               AGT_HOOKFMT_NODE, 
                               AGT_HOOK_TYPE_SETHOOK, 
                               sethook_callback);


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 /sethook-ex:trigger
*     Effect:  change 'trigger' node value to a custom value
*
*
*********************************************************************/
static status_t
    sethook_callback (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)
{
    log_debug("\nEnter SET Hook callback");

    status_t res = NO_ERR;
    val_value_t *errorval = (curval) ? curval : newval;

    const xmlChar *defpath =
        (const xmlChar *)"/sethook-ex:trigger";

     switch (editop) {
    case OP_EDITOP_LOAD:
        break;
    case OP_EDITOP_MERGE:
    case OP_EDITOP_REPLACE:
    case OP_EDITOP_CREATE:
        /* modify "/sethook-ex:trigger" value on the fly */
        if (newval &&
            !xml_strcmp(VAL_NAME(newval), (const xmlChar *)"trigger")) {

            /* This is just an example string value.
             * Generate you own custom value for a new node here.
             */
            const xmlChar *newval_str = (const xmlChar *)"delete-all";

            log_info("\n changing node:%s value:%s to value:%s \n",
                     VAL_NAME(newval),
                     VAL_STR(newval),
                     newval_str);

            /* change the value */
            res =
                val_set_simval(newval,
                               VAL_TYPDEF(newval),
                               VAL_NSID(newval),
                               VAL_NAME(newval),
                               newval_str);

            /* in the case we do NOT have to call add_edit() */
            (void)defpath;
            (void)txcb;
        }
        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 */


As a result, whenever some north bound agent creates or modify the /trigger leaf node, the callback will modify it's value to a custom one.


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="merge"
        xmlns="http://netconfcentral.org/ns/sethook-example">trigger-value</trigger>
    </config>
  </edit-config>


The following output may be seen during the operation described above. The three phase edit-config processing may look as follows:


***** start validate phase on candidate for session 3, transaction 2211 *****

Start container in validate commit for yuma-netconf:config
Start leaf in validate commit for sethook-example_modify:trigger
agt_acm: check write <trigger> allowed for user 'admin'
agt_acm: PERMIT (access-control off)
invoke_simval:validate: sethook-example_modify:trigger start
Start undo for 'sethook-example_modify:trigger'
Starting setup of transaction callbacks
Checking for validate user callback for merge edit on sethook-example_modify:trigger
Found validate user callback for merge:sethook-example_modify:trigger
agt_cfg: use undo Q for 'trigger'
agt_cfg: add undo nested_silcall for 'sethook-example_modify:trigger'
undo for merge op on sethook-example_modify:trigger
silcall for sethook-example_modify:trigger
Finished setup of transaction callbacks
Start invoking Set Hook callback for merge on sethook-example_modify:trigger
Enter SET Hook callback
 changing node:trigger value:trigger2 to value:delete-all

Finish invoking Set Hook callback on sethook-example_modify:trigger
Start invoking validate SIL callback for merge on sethook-example_modify:trigger
 (trigger) Enter EDIT-2 callback
 ------PHASE:validate;EDITOP:merge

Finished invoking user callback on sethook-example_modify:trigger

***** start apply phase on candidate for session 3, transaction 2211 *****

apply_write_val: trigger start
apply_write_val[3]: apply merge on /sethook-ex:trigger
start delete_dead_nodes2 for transaction
delete_dead_nodes2: undo for sethook-example_modify:trigger
start delete_dead_nodes for node 'sethook-example_modify:trigger'
start run_external_xpath_tests for 'sethook-example_modify:trigger'
start delete_extern_nodes for 'trigger'

***** start commit phase on candidate for session 3, transaction 2211 *****

Start full commit of transaction 2211: 1 edit on candidate config
edit-transaction 2211: on session 3 by [email protected]
  time: 2020-04-13T19:40:59Z
  message-id: 2
  trace-id: --
  datastore: candidate
  operation: merge
  target: /sethook-ex:trigger
  comment: none


To ensure that the data added by add_edit was successfully generated an application can retrieve running configurations after commit. The server should reply with:


 <data>
  <trigger xmlns="http://netconfcentral.org/ns/sethook-example">delete-all</trigger>
 </data>



NOTE: If your desired node is a leaf within a container or list you may want to register the Set Hook callback on the container or list and then find your desired value you want to modify or create and then perform desired actions. Otherwise, if you register Set Hook callback on the leaf node within container the server will not invoke Set Hook callbacks when you create the container.


Also, another solution is to make sure that the container or list already pre-exist and only after that you may edit desired leaf node in the container and apply desired actions.



EXAMPLE 2: Modify a container children values in progress



Consider the same data model as for the previous examples; however, now  we will register callback for the /interfaces/interface list node. In this  example, we will modify /interfaces/interface/state container's children values. The trigger for the edit will be creation of a new interface with specific name value.


    /* Register an object specific Set Hook callback */ 
    res = agt_cb_hook_register((const xmlChar*)"/sethook-ex:interfaces/sethook-ex:interface",
                               AGT_HOOKFMT_NODE,
                               AGT_HOOK_TYPE_SETHOOK,
                               sethook_callback2);


Assume we have the running datastore configured as follows:


  data {
    interfaces {
      interface  vlan1 {
        name vlan1
        state {
          admin-state false
        }
      }
      interface  vlan2 {
        name vlan2
      }
    }
  }


And now we want to modify "state" container value when a new interface with specific value is created. In our example, a new interface value will be "LAG" that will trigger a "state" container change.

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 /if:interfaces/if:interface[name='LAG']
*     Effect:  change /interfaces/interface[name='vlan1']/state
*              child to custom value
*
* RETURNS:
*    status
*********************************************************************/
static status_t
    sethook_callback2 (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;
    status_t res2 = NO_ERR;
    val_value_t *errorval = (curval) ? curval : newval;
    val_value_t *child_val = NULL;

    /* defpath specified target */
    const xmlChar *defpath =
        (const xmlChar *)"/interfaces/interface[name='vlan1']/state";

    if (newval) {
        child_val =
            val_find_child(newval,
                           y_sethook_example_M_sethook_example,
                           (const xmlChar *)"name");
        if (child_val) {
            log_debug("\n>>>>>>>>>> "
                      " callback for %s editop, name=%s",
                       op_editop_name(editop), VAL_STR(child_val));
        }
    }

    switch (editop) {
    case OP_EDITOP_LOAD:
        break;
    case OP_EDITOP_MERGE:
    case OP_EDITOP_REPLACE:
    case OP_EDITOP_CREATE:
        if (newval && !val_is_default(newval) && child_val &&
            !xml_strcmp(VAL_STR(child_val), (const xmlChar *)"LAG"))  {

            val_value_t *testval =
                agt_val_get_data(txcb->cfg_id,
                                 (const xmlChar *)"/interfaces/interface[name='vlan1']/state",
                                 &res2);

            val_value_t *editval = NULL;
            if (testval) {
                /* clone just found value in order to write a new edits */
                editval = val_clone_config_data(testval, &res);
                if (!editval) {
                    return ERR_NCX_INVALID_VALUE;
                } else {
                    /* Only when edit target is container or list.
                     * Need to clean any found children in order to
                     * make a new edit and a new value.
                     * Otherwise if the target children leaf exist in this
                     * container the val_add_child() will fail.
                     */
                    val_delete_children(editval);
                }
            } else {
                return ERR_NCX_INVALID_VALUE;
            }

            /* add any other children to target container, can add any amount
             * of children here.
             * In this example we add one leaf "admin-state" with value "true"
             */
            obj_template_t *chobj =
                obj_find_child(VAL_OBJ(testval),
                               y_sethook_example_M_sethook_example,
                               (const xmlChar *)"admin-state");
            if (chobj) {
                val_value_t *child =
                    val_make_simval_obj(chobj,
                                        (const xmlChar *)"true",
                                        &res);
                if (child) {
                    res = val_child_add(child, editval);
                }
            } else {
                res = ERR_NCX_INVALID_VALUE;
            }

            /* add a new edit on defpath and populate new entry */
            if (res == NO_ERR) {
                res = agt_val_add_edit(scb,
                                       msg,
                                       txcb,
                                       defpath,
                                       editval,
                                       OP_EDITOP_MERGE);
            }

            /* 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_callback2 */


As a result of the following edit-config, when some north bound agent creates a new interface with name "LAG", the callback will modify the interface with name "vlan1" and update the "admin-state" value. 


  <edit-config>
    <target>
      <candidate/>
    </target>
    <default-operation>merge</default-operation>
    <test-option>set</test-option>
    <config>
      <interfaces xmlns="http://netconfcentral.org/ns/sethook-example">
        <interface>
          <name
            xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"
            nc:operation="create">LAG</name>
        </interface>
      </interfaces>
    </config>
  </edit-config>

The following output may be seen during the operation described above. The three phase edit-config processing may look as follows:


***** start validate phase on candidate for session 3, transaction 2219 *****

Start container in validate commit for yuma-netconf:config
Start container in validate commit for sethook-example_modify:interfaces
agt_acm: check write <interfaces> allowed for user 'admin'
agt_acm: PERMIT (access-control off)
invoke_cpxval:validate: sethook-example_modify:interfaces start
Start list in validate commit for sethook-example_modify:interface
Convert merge to create for sethook-example_modify:interface
agt_acm: check write <interface> allowed for user 'admin'
agt_acm: PERMIT (access-control off)
invoke_cpxval:validate: sethook-example_modify:interface start
Start undo for 'sethook-example_modify:interface'
Start leaf in validate commit for sethook-example_modify:name
agt_acm: check write <name> allowed for user 'admin'
agt_acm: PERMIT (access-control off)
invoke_simval:validate: sethook-example_modify:name start
Extending edit2 parent SIL callback for child 'name'
Start child-undo for 'sethook-example_modify:name'
add_default: checking 'sethook-example_modify:interface'
add_node_default: checking 'sethook-example_modify:state'
add empty NP container 'sethook-example_modify:state'
start delete_default_dead_nodes for node 'sethook-example_modify:interface'
Starting setup of transaction callbacks
Checking for validate user callback for create edit on sethook-example_modify:interface
Found validate user callback for create:sethook-example_modify:interface
agt_cfg: use undo Q for 'interface'
agt_cfg: add undo nested_silcall for 'sethook-example_modify:interface'
Skipping callback on edit2 terminal sethook-example_modify:name
Checking for validate user callback for create edit on sethook-example_modify:state
Found validate user callback for create:sethook-example_modify:state
agt_cfg: use cur_silcall Q for 'state'
agt_cfg: add undo nested_silcall for 'sethook-example_modify:state'
undo for create op on sethook-example_modify:interface
silcall for sethook-example_modify:interface
 silcall for sethook-example_modify:state
Finished setup of transaction callbacks
Start invoking Set Hook callback for create on sethook-example_modify:interface
>>>>>>>>>>  callback for create editop, name=LAG
agt_val: retrieving '/interfaces/interface[name='vlan1']/state' from candidate datastore
xpath1: checking list lookup for 'sethook-example_modify:interface' w/ 1 key
Making list lookup for 'interface'
Made list lookup OK
Clearing result
Added real_listval 'interface'
eval_expr
xpath value result for '/interfaces/interface[name='vlan1']/state'
  typ: nodeset =
   node HDR sethook-ex:state

Start adding Set Hook edit for merge on /interfaces/interface[name='vlan1']/state
agt_val: retrieving '/interfaces/interface[name='vlan1']/state' from candidate datastore
xpath1: checking list lookup for 'sethook-example_modify:interface' w/ 1 key
Making list lookup for 'interface'
Made list lookup OK
Clearing result
Added real_listval 'interface'
eval_expr
xpath value result for '/interfaces/interface[name='vlan1']/state'
  typ: nodeset =
   node HDR sethook-ex:state

Start container in validate commit for sethook-example_modify:state
agt_acm: check write <state> allowed for user 'admin'
agt_acm: PERMIT (access-control off)
invoke_cpxval:validate: sethook-example_modify:state start
Start leaf in validate commit for sethook-example_modify:admin-state
agt_acm: check write <admin-state> allowed for user 'admin'
agt_acm: PERMIT (access-control off)
invoke_simval:validate: sethook-example_modify:admin-state start
Start undo for 'sethook-example_modify:state'
Start child-undo for 'sethook-example_modify:admin-state'
Finish invoking Set Hook callback on sethook-example_modify:interface
Start invoking validate SIL callback for create on sethook-example_modify:interface
 (interface) Enter EDIT-2 callback
 ------PHASE:validate;EDITOP:create

Finished invoking user callback on sethook-example_modify:interface
Start invoking validate SIL callback for create on sethook-example_modify:state
 (state) Enter EDIT-2 callback
 ------PHASE:validate;EDITOP:create

Finished invoking user callback on sethook-example_modify:state

***** start apply phase on candidate for session 3, transaction 2219 *****

apply_write_val: interface start
add_default: checking 'sethook-example_modify:interface'
add_node_default: checking 'sethook-example_modify:state'
add_default: checking 'sethook-example_modify:state'
apply_write_val[3]: apply create on /sethook-ex:interfaces/sethook-ex:interface[sethook-ex:name="LAG"]
Add child 'interface' to parent 'interfaces'
apply_write_val: state start
apply_write_val[3]: apply merge on /sethook-ex:interfaces/sethook-ex:interface[sethook-ex:name="vlan1"]/sethook-ex:state
start delete_dead_nodes2 for transaction
delete_dead_nodes2: undo for sethook-example_modify:interface
start delete_dead_nodes for node 'sethook-example_modify:interface'
start run_external_xpath_tests for 'sethook-example_modify:interface'
start delete_extern_nodes for 'interface'
start run_external_xpath_tests for 'sethook-example_modify:name'
start run_external_xpath_tests for 'sethook-example_modify:speed'
start run_external_xpath_tests for 'sethook-example_modify:hook-node'
start run_external_xpath_tests for 'sethook-example_modify:state'
start delete_extern_nodes for 'state'
start run_external_xpath_tests for 'sethook-example_modify:admin-state'
delete_dead_nodes2: undo for sethook-example_modify:state
start delete_dead_nodes for node 'sethook-example_modify:state'
start run_external_xpath_tests for 'sethook-example_modify:state'
start delete_extern_nodes for 'state'
start run_external_xpath_tests for 'sethook-example_modify:admin-state'

***** start commit phase on candidate for session 3, transaction 2219 *****

Start full commit of transaction 2219: 2 edits on candidate config
edit-transaction 2219: on session 3 by [email protected]
  time: 2020-04-13T20:10:20Z
  message-id: 2
  trace-id: --
  datastore: candidate
  operation: create
  target: /sethook-ex:interfaces/sethook-ex:interface[sethook-ex:name="LAG"]
  comment: none

edit-transaction 2219: on session 3 by [email protected]
  time: 2020-04-13T20:10:20Z
  message-id: 2
  trace-id: --
  datastore: candidate
  operation: merge
  target: /sethook-ex:interfaces/sethook-ex:interface[sethook-ex:name="vlan1"]/sethook-ex:state/sethook-ex:admin-state
  comment: none

Complete commit OK of transaction 2219 on candidate database


To ensure that the data added by add_edit was successfully generated an application can retrieve running configurations. The final running datastore result may look as follows after commit:


  data {
    interfaces {
      interface  vlan1 {
        name vlan1
        state {
          admin-state true
        }
      }
      interface  vlan2 {
        name vlan2
      }
      interface  LAG {
        name LAG
      }
    }
  }