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: Update a New Node


Important remark for this type of Set Hook callbacks. The Set Hook callback is very powerful tool and the callback function can be used to manipulate with new values in the edit-config. However, it is only supported to update values in the edit configuration when you CREATE a new values. It is NOT supported to update values for any other operation.

This means that you can update a new value in the edit when you create it. You can add and modify the new value. But cannot update a new value when there is already existent value for the same node. The MERGE edit on complex nodes is not supported and the server cannot merge add_edit edits complex values, edit-config requested values and already existent values in the datastore.



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_update --sil-get2 sil-edit2


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

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


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

  container subsystems {                      // edit2 cb
    presence true;

    list subsystem {                          // edit2 cb + Set Hook (Node)
      key "name";

      leaf name {
        type string;
      }

      container cpu-resource {                // edit2 cb

        list socket {                         // edit2 cb
          key "socket-id";

          leaf socket-id {
            type uint32;
          }

          container cpu {                     // edit2 cb
            leaf num-of-cores {
              type uint32;
            }
          }
        }
      }

      container worker-threads {              // edit2 cb
        list worker-thread {                  // edit2 cb
          key "thread-name";

          leaf thread-name {
            type string {
              length "1..128";
            }
          }
        }
      }
    }
  }
}



Example 1



Assume we registered Set Hook callback for the “interfaces” container node. Thus, whenever the node /interfaces node is edited, the Set Hook callback function will be called and additional specific data can be populated with desired values.

In this example, we will generate an extra “status” leaf when we create a new "interfaces" container. 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: create /interfaces
*       create container and populate extra nodes
*
*   add_edit effect: populate extra nodes within the container
*        when /interfaces is created
*
*********************************************************************/
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;

    /* defpath specified target root */
    const xmlChar *defpath =
        (const xmlChar *)"/sethook-ex:interfaces";

    switch (editop) {
    case OP_EDITOP_LOAD:
        break;
    case OP_EDITOP_MERGE:
    case OP_EDITOP_REPLACE:
        break;
    case OP_EDITOP_CREATE:
        /* populate a new container with several leafs */
        if (newval) {

            /* use newval value to write a new edits */
            val_value_t *editval =
                val_clone_config_data(newval, &res);
            if (editval == NULL) {
                return ERR_INTERNAL_MEM;
            }

            /* add any other children to target container, can add any amount
             * of children here.
             */
            obj_template_t *chobj =
                obj_find_child(VAL_OBJ(editval),
                               y_sethook_example_M_sethook_example,
                               (const xmlChar *)"status");
            if (chobj) {
                val_value_t *child =
                    val_make_simval_obj(chobj,
                                        (const xmlChar *)"extra-leaf",
                                        &res);
                if (child) {
                    res = val_child_add(child, editval);
                }
            } else {
                res = ERR_NCX_NOT_FOUND;
            }

            if (res == NO_ERR) {
                /* add a new edit on defpath and populate new entries */
                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_callback */



NOTE: Callback format should be AGT_HOOKFMT_NODE in order to invoke this callback only for a new node edit. If the format is AGT_HOOKFMT_SUBTREE, the callback will be invoked if you edit children as well. In this case newval will not represent container value and cannot not be used to construct a new updated edit value.



As a result, whenever some north bound agent edit the /interfaces container with a specific value, the callback is invoked and additionally adds an extra leaf within the same container. 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>
      <interfaces xmlns="http://netconfcentral.org/ns/sethook-example">
        <interface>
          <name
            xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"
            nc:operation="create">vlan2</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 2187 *****

Start container in validate commit for yuma-netconf:config
Start container in validate commit for sethook-example:interfaces
agt_acm: check write <interfaces> allowed for user 'admin'
agt_acm: PERMIT (access-control off)
invoke_cpxval:validate: sethook-example:interfaces start
Start undo for 'sethook-example:interfaces'
Start list in validate commit for sethook-example:interface
agt_acm: check write <interface> allowed for user 'admin'
agt_acm: PERMIT (access-control off)
invoke_cpxval:validate: sethook-example:interface start
Start child-undo for 'sethook-example:interface'
Start leaf in validate commit for sethook-example:name
agt_acm: check write <name> allowed for user 'admin'
agt_acm: PERMIT (access-control off)
invoke_simval:validate: sethook-example:name start
Extending edit2 parent SIL callback for child 'name'
Start child-undo for 'sethook-example:name'
add_default: checking 'sethook-example:interfaces'
add_node_default: checking 'sethook-example:interface'
add_default: checking 'sethook-example:interface'
add_node_default: checking 'sethook-example:state'
add empty NP container 'sethook-example:state'
start delete_default_dead_nodes for node 'sethook-example:interfaces'
Starting setup of transaction callbacks
Checking for validate user callback for create edit on sethook-example:interfaces
Found validate user callback for create:sethook-example:interfaces
agt_cfg: use undo Q for 'interfaces'
agt_cfg: add undo nested_silcall for 'sethook-example:interfaces'
Checking for validate user callback for create edit on sethook-example:interface
Found validate user callback for create:sethook-example:interface
agt_cfg: use cur_silcall Q for 'interface'
agt_cfg: add undo nested_silcall for 'sethook-example:interface'
Skipping callback on edit2 terminal sethook-example:name
Checking for validate user callback for create edit on sethook-example:state
Found validate user callback for create:sethook-example:state
agt_cfg: use cur_silcall Q for 'state'
agt_cfg: add undo nested_silcall for 'sethook-example:state'
undo for create op on sethook-example:interfaces
silcall for sethook-example:interfaces
 silcall for sethook-example:interface
  silcall for sethook-example:state
Finished setup of transaction callbacks


Start invoking Set Hook callback for create on sethook-example:interfaces
Enter SET Hook callback
Start adding Set Hook edit for merge on /sethook-ex:interfaces
add_edit: expand container undo for 'sethook-example:interfaces'
add_edit: Start silcall update for Added Edits
Checking for validate user callback for create edit on sethook-example:interfaces
Found validate user callback for create:sethook-example:interfaces
agt_cfg: use undo Q for 'interfaces'
agt_cfg: reuse nested_silcall for 'sethook-example:interfaces'
Checking for validate user callback for create edit on sethook-example:interface
Found validate user callback for create:sethook-example:interface
agt_cfg: use cur_silcall Q for 'interface'
agt_cfg: add undo nested_silcall for 'sethook-example:interface'
Skipping callback on edit2 terminal sethook-example:name
Checking for validate user callback for create edit on sethook-example:state
Found validate user callback for create:sethook-example:state
agt_cfg: use cur_silcall Q for 'state'
agt_cfg: add undo nested_silcall for 'sethook-example:state'
Skipping callback on edit2 terminal sethook-example:status
undo for create op on sethook-example:interfaces
silcall for sethook-example:interfaces
 silcall for sethook-example:interface
  silcall for sethook-example:state
add_edit: Finish silcall update for Added Edits
Finish invoking Set Hook callback on sethook-example:interfaces


Start invoking validate SIL callback for create on sethook-example:interfaces
 (interfaces) Enter EDIT-2 callback
 ------PHASE:validate;EDITOP:create

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

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

Finished invoking user callback on sethook-example:state

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

apply_write_val: interfaces start
add_default: checking 'sethook-example:interfaces'
add_node_default: checking 'sethook-example:interface'
add_default: checking 'sethook-example:interface'
add_node_default: checking 'sethook-example:state'
add_default: checking 'sethook-example:state'
apply_write_val[3]: apply create on /sethook-ex:interfaces
start delete_dead_nodes2 for transaction
delete_dead_nodes2: undo for sethook-example:interfaces
start delete_dead_nodes for node 'sethook-example:interfaces'
start run_external_xpath_tests for 'sethook-example:interfaces'
start delete_extern_nodes for 'interfaces'
start run_external_xpath_tests for 'sethook-example:interface'
start delete_extern_nodes for 'interface'
start run_external_xpath_tests for 'sethook-example:name'
start run_external_xpath_tests for 'sethook-example:speed'
start run_external_xpath_tests for 'sethook-example:hook-node'
start run_external_xpath_tests for 'sethook-example:state'
start delete_extern_nodes for 'state'
start run_external_xpath_tests for 'sethook-example:admin-state'
start run_external_xpath_tests for 'sethook-example:status'

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

Start full commit of transaction 2187: 1 edit on candidate config
edit-transaction 2187: on session 3 by admin@127.0.0.1
  time: 2020-04-12T23:34:24Z
  message-id: 2
  trace-id: --
  datastore: candidate
  operation: create
  target: /sethook-ex:interfaces
  comment: none

Complete commit OK of transaction 2187 on candidate database


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


 <data>
  <interfaces xmlns="http://netconfcentral.org/ns/sethook-example">
   <interface>
    <name>vlan2</name>
   </interface>
   <status>extra-leaf</status>
  </interfaces>
 </data>



EXAMPLE 2


Assume we registered Set Hook callback for the “subsystem” list node. Thus, whenever the node /subsystems/subsystem node is edited, the Set Hook callback function will be called and additional specific data can be populated with desired values.

In this example, we will generate an extra /subsystems/subsystem/worker-threads container when we create a new /subsystems/subsystem node. The callback function may look as follows:


/********************************************************************
* FUNCTION sethook_callback2
*
* Callback function for server object handler
* Used to provide a callback for a specific named object
*
* Set-Hook: (
*   format: NODE; SUBTREE is NOT supported
*   trigger: CREATE
*      path: /subsystems/subsystem[name='virtual']
*
*   add_edit effect: populate extra container
*        when /subsystems/subsystem is created
*
* 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
    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;
    val_value_t *errorval = (curval) ? curval : newval;

    handle_other(newval, curval);

    val_value_t *editval = NULL;
    obj_template_t *cont_obj = NULL;
    obj_template_t *list_obj = NULL;

    if (newval) {
        cont_obj =
            obj_find_child(VAL_OBJ(newval),
                           y_sethook_example_M_sethook_example,
                           (const xmlChar *)"worker-threads");

        if (cont_obj) {
            list_obj =
                obj_find_child(cont_obj,
                               y_sethook_example_M_sethook_example,
                               (const xmlChar *)"worker-thread");
        }
    }

    /* defpath specified target root */
    const xmlChar *defpath =
        (const xmlChar *)"/subsystems/subsystem[name='virtual']";

    switch (editop) {
    case OP_EDITOP_LOAD:
        break;
    case OP_EDITOP_MERGE:
    case OP_EDITOP_REPLACE:
        break;
    case OP_EDITOP_CREATE:
        if (newval && cont_obj && list_obj)  {

            log_info("\n\n Start adding Edit value for worker-thread");

            /* clone value in order to write a new edits */
            editval = val_clone_config_data(newval, &res);
            if (!editval) {
                return ERR_NCX_INVALID_VALUE;
            }

            /* now can add more children to the newval */

            val_value_t *cont_val = val_new_value();
            if (!cont_val) {
                return ERR_INTERNAL_MEM;
            }
            val_init_from_template(cont_val, cont_obj);

            /* Create list entries */
            val_value_t *list_value =
                create_list_entry(list_obj,
                                  (const xmlChar *)"thread-name",
                                  (const xmlChar *)"worker-0-1",
                                  &res);
            if (!list_value) {
                log_error("\ncreate_list_entry failed, res %d", res);
                val_free_value(cont_val);
                val_free_value(editval);
                return res;
            }

            res = val_child_add(list_value, cont_val);
            if (res == NO_ERR) {
                res = val_child_add(cont_val, editval);
                if (res == ERR_NCX_DUP_ENTRY) {
                    /* In case NP container already exist (was created by the server
                     * as a default NP-container)
                     * clean up the value before adding a new one
                     */
                    val_value_t *chval =
                        val_find_child_fast(editval,
                                            obj_get_nsid(VAL_OBJ(cont_val)),
                                            obj_get_name(VAL_OBJ(cont_val)));
                    if (chval && val_is_default(chval)) {
                        val_remove_child(chval);
                        val_free_value(chval);
                        chval = NULL;

                        res = val_child_add(cont_val, editval);
                    }
                }
            }

            log_info("\n\n Edit value - added container to value");
            val_dump_value(editval, 3, DBG4);

            /* 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_CREATE);
            }

            /* 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, whenever some north bound agent creates the /subsystems/subsystem, the callback is invoked and additionally creates a new  /subsystems/subsystem/worker-threads container. 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>
  <error-option>stop-on-error</error-option>
  <test-option>set</test-option>
  <config>
    <subsystems xmlns="http://netconfcentral.org/ns/sethook-example">
      <subsystem>
        <name>virtual</name>
        <cpu-resource>
          <socket>
            <socket-id>0</socket-id>
            <cpu>
              <num-of-cores>1</num-of-cores>
            </cpu>
          </socket>
        </cpu-resource>
      </subsystem>
      <subsystem>
        <name>virtual2</name>
        <cpu-resource>
          <socket>
            <socket-id>0</socket-id>
            <cpu>
              <num-of-cores>1</num-of-cores>
            </cpu>
          </socket>
        </cpu-resource>
      </subsystem>
    </subsystems>
  </config>
</edit-config>


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


 <data>
  <subsystems xmlns="http://netconfcentral.org/ns/sethook-example">
   <subsystem>
    <name>virtual</name>
    <cpu-resource>
     <socket>
      <socket-id>0</socket-id>
      <cpu>
       <num-of-cores>1</num-of-cores>
      </cpu>
     </socket>
    </cpu-resource>
    <worker-threads>
     <worker-thread>
      <thread-name>worker-0-1</thread-name>
     </worker-thread>
    </worker-threads>
   </subsystem>
   <subsystem>
    <name>virtual2</name>
    <cpu-resource>
     <socket>
      <socket-id>0</socket-id>
      <cpu>
       <num-of-cores>1</num-of-cores>
      </cpu>
     </socket>
    </cpu-resource>
    <worker-threads>
     <worker-thread>
      <thread-name>worker-0-1</thread-name>
     </worker-thread>
    </worker-threads>
   </subsystem>
  </subsystems>
 </data>