The server supports 2 modes of database editing callbacks.The original mode (EDIT1) is designed to invoke data node callbacks at the leaf level.

This means that each altered leaf will cause a separate SIL callback.If no leaf callbacks are present, then the parent node will be invoked multiple times.

The EDIT2 mode is “list-based” or “container-based” instead. The following key aspects define EDIT2 callbacks:

  • In this mode there are no SIL callback functions expected for terminal nodes (leaf, leaf-list, anyxml).

  • The SIL callback for a container or list will be invoked, even if only child terminal nodes are being changed.

  • The parent SIL callback will be invoked only once (per phase) if multiple child nodes are being altered at once

  • The parent node for such an edit is flagged in the “undo” record as a special “edit2_merge”. The edit operation will be “OP_EDITOP_MERGE”, but the parent node is not being changed

  • The special “edit2_merge” type of the edit will have a queue of child_undo records containing info on the child edits. For example, 1 leaf could be created, another leaf modified, and a third leaf deleted, all in the same edit request. The child_undo records provide the edit operation and values being changed.


The EDIT2 callback function is hooked into the server with the agt_cb_register_edit2_callback function,described below. The SIL code generated by yangdump-pro uses this function to register a single callback function for all callback phases.

The registration is done during the Initialization Phase 1 before the startup configuration is loaded into the running configuration database and before running configurations are loaded.

Initialization function with EDIT2 callback registration may look as follows:



Register EDIT2 Callback


extern status_t
    agt_cb_register_edit2_callback(const xmlChar *modname,
                                   constxmlChar *defpath,
                                   constxmlChar *version,
                                   agt_cb_fn_tcbfn);



Parameter
Description
modname
Module name string that defines this object node.
defpath
Absolute path expression string indicating which node the callback function is for.
version
If non-NULL, indicates the exact module version expected.
cbfn
The callback function address.  This function will be used for all callback phases.

EDIT2 Callback Cleanup


extern void
    agt_cb_unregister_callbacks(const xmlChar *modname,
                                const xmlChar *defpath);


Parameter
Description
modname
Module name string that defines this object node.
defpath
Absolute path expression string indicating which node the callback function is for.

If you register a callback for a specific object,your SIL code should unregister it during the cleanup phase, that is being called any time the server is shutting down. Also, it is getting called during a restart and reload procedure.



NOTE:

The unregister function can be called just once for a specific object. It will unregister EDIT1, EDIT2, and even GET2 callbacks for the object. However, if it is called multiple times for the same object, it will return with NO errors.

All other callbacks that the object may hold should be unregistered separately.



The EDIT2 callback template is the same as the EDIT1 callback. The difference is that there is a queue of child edit records that may need to be accessed to reliably process the edit requests.

In the following example, EDIT2callback code gets each child_undo record in order to retrieve the real edited nodes and the edited operation and merely form the data and prints it to the log. Instead of printing the data an agent could process to the next step and run device instrumentation as required.

The following example code illustrates how the EDIT2 callback may look like:


/********************************************************************
* FUNCTION  edit2_callback_example
*
* Callback function for server object handler
* Used to provide a callback sub-mode for
* a specific named object
*
* Path: /interfaces/interface
* Add object instrumentation in COMMIT phase.
*
********************************************************************/
    edit2_callback_example (ses_cb_t *scb,
                            rpc_msg_t *msg,
                            agt_cbtyp_t cbtyp,
                            op_editop_t editop,
                            val_value_t *newval,
                            val_value_t *curval)
{
    status_t res = NO_ERR;
    val_value_t *errorval = (curval) ? curval : newval;

    if (LOGDEBUG) {
        log_debug("\nEnter edit2_callback_example callback for %s phase",
            agt_cbtype_name(cbtyp));
    }

    switch (cbtyp) {
    case AGT_CB_VALIDATE:
        /* description-stmt validation here */
        break;
    case AGT_CB_APPLY:
        /* database manipulation done here */
        break;
    case AGT_CB_COMMIT:
        /* device instrumentation done here */
        switch (editop) {
        case OP_EDITOP_LOAD:
            break;
        case OP_EDITOP_MERGE:
            /* the edit is not really on this node; need to get
             * each child_undo record to get the real edited nodes
             * and the edited operations
             */
            agt_cfg_transaction_t *txcb = RPC_MSG_TXCB(msg);
            agt_cfg_undo_rec_t *child_edit =
                agt_cfg_first_child_edit(txcb, newval, curval);

            while (child_edit) {
                op_editop_t child_editop = OP_EDITOP_NONE;
                val_value_t *child_newval = NULL;
                val_value_t *child_curval = NULL;
                xmlChar *newval_str = NULL;
                xmlChar *curval_str = NULL;

                agt_cfg_child_edit_fields(child_edit,
                                          &child_editop,
                                          &child_newval,
                                          &child_curval);

                val_value_t *useval =
                    child_newval ? child_newval : child_curval;
                if (!useval) {
                    res = ERR_NCX_INVALID_VALUE;
                    break;
                }

                switch (child_editop) {
                case OP_EDITOP_LOAD:
                    break;
                case OP_EDITOP_MERGE:
                case OP_EDITOP_REPLACE:
                    /* use both child_newval and child_curval here*/
                    break;
                case OP_EDITOP_CREATE:
                    /* use only child_newval. child_curval can be NULL here */
                    break;
                case OP_EDITOP_REMOVE:
                case OP_EDITOP_DELETE:
                    /* use only child_curval. child_newval can be NULL here */
                    if (useval &&
                        !xml_strcmp(VAL_NAME(useval),
                                    (const xmlChar *)"untagged-ports") &&
                        !xml_strcmp(VAL_STR(useval),
                                    (const xmlChar *)"not-supported")) {

                        res = ERR_NCX_OPERATION_NOT_SUPPORTED;
                    }
                    break;
                default:
                    res = SET_ERROR(ERR_INTERNAL_VAL);
                }

                log_info("\n        %s: editop=%s newval=%s curval=%s",
                      VAL_NAME(useval),
                      child_editop ? op_editop_name(child_editop) : NCX_EL_NONE,
                      child_newval ? newval_str : NCX_EL_NULL,
                      child_curval ? curval_str : NCX_EL_NULL);

                m__free(newval_str);
                m__free(curval_str);

                /* Force Rollback if the child value is not acceptable */
                if (res != NO_ERR) {
                    break;
                }

                /**** process child edits here ****/

                child_edit = agt_cfg_next_child_edit(child_edit);
            }

            break;
        case OP_EDITOP_REPLACE:
            /* the edit is on this list node and the child editop
             * can be treated the same as the parent (even if different)
             * the val_value_t child nodes can be accessed and there
             * are no child_undo records to use.
             *
             * Access only terminal nodes here, for example, and
             * run your instrumentation on these nodes.
             */
            val_value_t *child = val_get_first_terminal_child(newval);
            for (; child;
                   child = val_get_next_terminal_child(child)) {

                /**** process child leaf nodes here ****/
            }

            break;
        case OP_EDITOP_CREATE:
            /* the edit is on this list node and the child editop
             * can be treated the same as the parent (even if different)
             * the val_value_t child nodes can be accessed and there
             * are no child_undo records to use
             */
            val_value_t *child_newval = NULL;
            child_newval =
                val_find_child(newval,
                               EXAMPLE_MODNAME,
                               (const xmlChar *)"untagged-ports");

            val_value_t *leaflist_val = child_newval;
            while (leaflist_val) {

                /**** process child leaf-list nodes here ****/

                leaflist_val = val_next_child_same(leaflist_val);
            }

            break;
        case OP_EDITOP_DELETE:
            break;
        default:
            res = SET_ERROR(ERR_INTERNAL_VAL);
        }
        break;
    case AGT_CB_ROLLBACK:
        /* undo device instrumentation here */
        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;

} /* edit2_callback_example */



NOTE:

Do not use SET_ERROR() macro to return normal errors. This macro will cause “assert()” to be invoked, halting program execution. The macro defined in netconf/src/ncx/status.h and it is for flagging internal errors only. This macro must not be used for normal errors.



Now, let us go through the most important parts of the above example.

In the following part of the above code example we are trying to write a log with the information that the callback is getting called. This is completely optional and is not required code,the main purpose of this code is to provide fast way to debug problems.



...

   if (LOGDEBUG) {
        log_debug("\nEnteredit2_callback_example callback for %s phase",
            agt_cbtype_name(cbtyp));
   }

...


The agt_cbtype_name API function merely gets the string for the server callback phase to print it out. 



NOTE:

If the operation is “merge”, the edit is not really on this node; need to get each child_undo record to get the real edited nodes and the edited operations.

Otherwise, the edit is on the list node and the child edit operation can be treated the same as the parent (even if different) the val_value_t child nodes can be accessed and there are no child_undo records to use.



The following part of the above example code is the most important in the EDIT2 callbacks.If the operation is set to “merge”, it means that some of the children of the current node have been modified. The edit is not really on the current node, and we need to get each child undo record to get the real edited nodes and the edited operations.

In order to do so, we need to loop through the child undo records and retrieve the actual operation and the actual child node that has been modified. 



NOTE:

If the parent operation is “merge” and the actual edit is on the node with default value. The edited node has a “default” YANG statement, then the actual child edit operation will always be “merge”. The difference will be in the newval, curval values.



The default children nodes should be handled carefully. If the edited node has “default” YANG statement, then the actual child edit operation will always be “merge” and curval, newval will always be set. The difference will be in newval, curval values. The following table illustrates what values will be set for those nodes:



Action
Newval value
Curval value

create default node

new non-default value

default value

modify default node

new non-default or set back to default value

old non-default value

delete default node

default value

old non-default value

The following code demonstrates how to loop through the child undo records and retrieve the actual operation and the actual child nodes.After the retrieval, the code merely logs the edited nodes into the log file and, in addition, it validates a child value to make sure that the value is appropriate for the node.



  ...
        case OP_EDITOP_MERGE:
            agt_cfg_transaction_t *txcb = RPC_MSG_TXCB(msg);
            agt_cfg_undo_rec_t *child_edit =
                agt_cfg_first_child_edit(txcb, newval, curval);

            while (child_edit) {
                op_editop_t child_editop = OP_EDITOP_NONE;
                val_value_t *child_newval = NULL;
                val_value_t *child_curval = NULL;
                xmlChar *newval_str = NULL;
                xmlChar *curval_str = NULL;

                agt_cfg_child_edit_fields(child_edit,
                                          &child_editop,
                                          &child_newval,
                                          &child_curval);

                if (child_newval) {
                    newval_str = val_make_sprintf_string(child_newval);
                    if (newval_str == NULL) {
                        return ERR_INTERNAL_MEM;
                    }
                }
                if (child_curval) {
                    curval_str = val_make_sprintf_string(child_curval);
                    if (curval_str == NULL) {
                        m__free(newval_str);
                        return ERR_INTERNAL_MEM;
                    }
                }

                val_value_t *useval =
                    child_newval ? child_newval : child_curval;
                if (!useval) {
                    return ERR_NCX_INVALID_VALUE;
                }

                switch (child_editop) {
                case OP_EDITOP_LOAD:
                    break;
                case OP_EDITOP_MERGE:
                case OP_EDITOP_REPLACE:
                    /* use both child_newval and child_curval here*/
                    break;
                case OP_EDITOP_CREATE:
                    /* use only child_newval. child_curval can be NULL here */
                    break;
                case OP_EDITOP_REMOVE:
                case OP_EDITOP_DELETE:
                    /* use only child_curval. child_newval can be NULL here */
                    if (useval &&
                        !xml_strcmp(VAL_NAME(useval),
                                    (const xmlChar *)"untagged-ports") &&
                        !xml_strcmp(VAL_STR(useval),
                                    (const xmlChar *)"not-supported")) {

                        res = ERR_NCX_OPERATION_NOT_SUPPORTED;
                    }
                    break;
                default:
                    res = SET_ERROR(ERR_INTERNAL_VAL);
                }

                log_info("\n        %s: editop=%s newval=%s curval=%s",
                      VAL_NAME(useval),
                      child_editop ? op_editop_name(child_editop) : NCX_EL_NONE,
                      child_newval ? newval_str : NCX_EL_NULL,
                      child_curval ? curval_str : NCX_EL_NULL);

                m__free(newval_str);
                m__free(curval_str);


                /* Force Rollback if the child value is not acceptable */
                if (res != NO_ERR) {
                    break;
                }

                /**** process child edits here ****/


                child_edit = agt_cfg_next_child_edit(child_edit);
            }

            break;
  ...



The following code illustrates how to loop through the children undo records in order to retrieve the actual operation and the actual edited children:


  ...

            agt_cfg_undo_rec_t *child_edit = 
                agt_cfg_first_child_edit(txcb, newval, curval); 

            while (child_edit) { 
                op_editop_t child_editop = OP_EDITOP_NONE; 
                val_value_t *child_newval = NULL; 
                val_value_t *child_curval = NULL; 

                agt_cfg_child_edit_fields(child_edit, 
                                          &child_editop, 
                                          &child_newval, 
                                          &child_curval); 

              /**** process child edits here ****/ 

                child_edit = agt_cfg_next_child_edit(child_edit); 
            } 

  ...




The agt_cfg_first_child_edit, agt_cfg_next_child_edit, agt_cfg_child_edit_fields API functions get the first, next child node edit record, and this edit record fields respectively. There is no any alternative functions that could be used to retrieve the desired child undo records. This loop merely loops trough all the edited children and is trying to retrieve their fields, such as child new value, current value, and an actual operation that have been applied to the child node.

After we retrieve any children edits, we can process them as required. The following example code demonstrates how to log the edited nodes into the log file:



  ...
                if (child_newval) { 
                    newval_str = val_make_sprintf_string(child_newval); 
                    if (newval_str == NULL) { 
                        return ERR_INTERNAL_MEM; 
                    } 
                } 
                if (child_curval) { 
                    curval_str = val_make_sprintf_string(child_curval); 
                    if (curval_str == NULL) {
                        m__free(newval_str); 
                        return ERR_INTERNAL_MEM; 
                    } 
                } 

                val_value_t *useval =
                    child_newval ? child_newval : child_curval;
                if (!useval) {
                    return ERR_NCX_INVALID_VALUE;
                }

                log_info("\n        %s: editop=%s newval=%s curval=%s", 
                      VAL_NAME(useval),
                      child_editop ? op_editop_name(child_editop) : NCX_EL_NONE, 
                      child_newval ? newval_str : NCX_EL_NULL, 
                      child_curval ? curval_str : NCX_EL_NULL); 

                m__free(newval_str); 
                m__free(curval_str); 

  ...



The val_make_sprintf_string API function malloc a buffer and fill it with a zero-terminated string representation of the value node. Make sure that the malloced buffer is freed after you use it. Use m__free to free the malloced buffer.

In the following part of the above code example we are trying to validate previously retrieved child “untagged-ports” node value. If the provided in the <edit-config> value is not acceptable as specified below, then the status_t res pointer will be set to ERR_NCX_OPERATION_NOT_SUPPORTED enumeration value, that would signal to record an error and rollback the <edit-config> operation.


  ...
                /* use only child_curval. child_newval can be NULL here */
                if (useval &&
                    !xml_strcmp(VAL_NAME(useval), 
                                (const xmlChar *)"untagged-ports") &&
                    !xml_strcmp(VAL_STR(useval), 
                                (const xmlChar *)"not-supported")) { 

                    res = ERR_NCX_OPERATION_NOT_SUPPORTED;
                } 
                break;
  ...


If the operation is not “merge”, it mean that the current <edit-config>is on the “interface” list and not on its children. The edit is on the list node and the child edit operation can be treated the same as the parent (even if different) the val_value_t child nodes can be accessed the same way as for EDIT1 callback functions and there are no child_undo records to use. The following code break illustrates how to access “untagged-ports” leaf-list children that are getting created along with their “interface” parent:


  ...

        case OP_EDITOP_CREATE: 
            val_value_t *child_newval = NULL; 
            child_newval = 
                val_find_child(newval, 
                               EXAMPLE_MODNAME, 
                               (const xmlChar *)"untagged-ports"); 

                val_value_t *leaflist_val = child_newval; 
                while (leaflist_val) { 

                    /**** process child leaf-list edits here ****/ 

                    leaflist_val = val_next_child_same(leaflist_val); 
                } 

                /**** process other child edits here if needed ****/ 

            break; 

  ...


The val_next_child_same API function getthe next instance of the corresponding child node.This API function is useful for leaf-list and list in order to get the next sibling in the list or leaf-list.



To summarize, any time an <edit-config>operation creates a new or modifies an existent “interface” list node or its children the callback will be invoked and run. As described earlier, if the <edit-config> operation creates a new list entry, the operation will be “create” and any children that are getting created along with its parent should be treated as regular nodes and should be accessed the same way as for EDIT1callback. However, if the operation is “merge”, the actual operation and edit is on children nodes, so the according code and actions should be applied.


Assuming the “interfaces” container and “interface” list pre-exist in the datastore. However, assume there is not any other children on this list other than the key child. To invoke the callback with “merge” operation the following RPC can be sent:


     <rpc message-id="101"
          xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
       <edit-config>
         <target>
           <candidate/>
         </target>
         <config>
           <interfaces xmlns="http://yumaworks.com/ns/ietf-interfaces-example">
              <interface>
                 <name>supported</name>

                 <untagged-ports>port1</untagged-ports>
                 <untagged-ports>port2</untagged-ports>
                 <untagged-ports>port3</untagged-ports>
                 <untagged-ports>port4</untagged-ports>

              </interface>
           </interfaces>

         </config>
       </edit-config>
     </rpc>


As a result, the server will invoke the callback with “merge” operation; however, the actual operation is “create”and the actual edit is on “untagged-ports” leaf-list. So, you will have to loop trough the children and retrieve the actual edits.

When the incoming configuration has been safely loaded onto the candidate datastore and validated, it is ready to impact the running system. If the device supports the :candidate capability, use the <commit> operation to apply candidate configuration to running datastore. Otherwise, if the device supports :writable-running capability, in the above <edit-config> operation example, change target to running.

This RPC and the callback function that it triggers will pass the validation and should not trigger any Rollback, so the server may reply with the following:


     <rpc-reply message-id="101"
                xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
       <ok/>
     </rpc-reply>


If we add another leaf-list entry to the<edit-config> RPC with the “not-supported” value, then the server should reply with the following error (based on the SIL callback validation that we created previously) and trigger Rollback phase where it should reverse any previously applied edits and restore the state to the state that was before the edit:


     <errors xmlns:ncx="http://netconfcentral.org/ns/yuma-ncx" 
             xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"> 
        <error> 
        <error-type>rpc</error-type> 
        <error-tag>operation-not-supported</error-tag> 
        <error-app-tag>no-support</error-app-tag> 
        <error-message xml:lang="en">operation not supported</error-message> 
        <error-info> 
          <error-number>273</error-number> 
        </error-info> 
        </error> 
     </errors> 


Now that the incoming configuration has been integrated into the running configuration and all the callbacks run their validation and processed all the nodes, the application needs to gain trust that the change has affected the device in the way intended without affecting it negatively. To gain this confidence, the application can run tests of the operational state of the device.  The nature of the test is dependent on the nature of the change and is outside the scope of this article. Depending on the changes that have been applied on running datastore, these tests may include reachability from the system running the application (using ping), changes in reachability to the rest of the network (by comparing the device's routing table), or, for instance, inspection of the particular change (looking for operational evidence of the BGP peer that was just added).