The following article illustrates how to utilize the EDIT1 callbacks in examples.


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 EDIT2 callbacks are out of the scope of this article.

Refer to How do I use EDIT2 callbacks for more references.


The following function template definition is used for EDIT1 andEDIT2 callback functions:


/* Typedef of the EDIT1/2 callback functions */
typedef status_t 
    (*agt_cb_fn_t) (ses_cb_t  *scb, 
                    rpc_msg_t *msg, 
                    agt_cbtyp_t cbtyp, 
                    op_editop_t  editop, 
                    val_value_t  *newval, 
                    val_value_t  *curval);


The EDIT1 callback function is hooked into the server with the agt_cb_register_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 the EDIT1 callbacks registration may look as follows.


Register EDIT1 Callback


extern status_t 
    agt_cb_register_callback (const xmlChar *modname,
                              const xmlChar *defpath,
                              const xmlChar *version,
                              agt_cb_fn_t cbfn);


ParameterDescription
modnameModule name string that defines this object node.
defpathAbsolute path expression string indicating which node the callback function is for.
versionIf non-NULL, indicates the exact module version expected.
cbfnThe callback function address.  This function will be used for all callback phases.


EDIT1 Callback Cleanup


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



ParameterDescription
modnameModule name string that defines this object node.
defpathAbsolute 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.


Example


Assume we use a YANG module that is attached in this article example.yang. This module has a simple schema, such as container with a few leafs.


In the example below, the callback code forces Rollback Phase if a new value of an system/host-name which is a container/leaf value is not acceptable (for some internal reason). Otherwise an agent can process to the next step and run device instrumentation as required. The callback function for this leaf is u_example_system_host_name_edit(), refer to the attached SIL code for more details.


A new validation, in this example, is done during “commit” phase. A new value is already written to the datastore (value is getting written during apply phase) which means the server will have to reverse the edit. The server will automatically delete just created new node element from the datastore and restore the state to the initial state, to the state before the edit.


To summarise, the following EDIT1 callback function will return an error if the value of the leaf host-name will be equal to "not-supported" string. It will fail during CREATE or MERGE operations only and only during the COMMIT Phase:


/********************************************************************
* FUNCTION u_example_system_host_name_edit
*
* Edit database object callback
* Path: leaf /system/host-name
* Add object instrumentation in COMMIT phase.
*
* INPUTS:
*     see agt/agt_cb.h for details
*
* RETURNS:
*     error status
********************************************************************/
status_t
    u_example_system_host_name_edit (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;
    const xmlChar *newval_val = (newval) ? VAL_STRING(newval) : 0;

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

    /* not used in this example */
    (void)scb;
    (void)msg;
    (void)curval;

    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_REPLACE:
            break;
        case OP_EDITOP_CREATE:
        case OP_EDITOP_MERGE:

             /* Force Rollback if the value is not acceptable */
            if (newval &&
                !xml_strcmp(newval_val, (const xmlChar *)"not-supported"))  {

                log_info("\n\n Value is not supported for %s editop, host-name=%s",
                         op_editop_name(editop),
                         newval_val);

                /* Validation failed if a value is not supported */
                res = ERR_NCX_OPERATION_NOT_SUPPORTED;
            } else {
                /* Run device instrumentation here  */
            }

            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);
    }
    return res;

} /* u_example_system_host_name_edit */


Now, let us go through the most eminent parts of the above example. In the following part of the above code example we are trying to validate actual value of host-name. 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.


             /* Force Rollback if the value is not acceptable */
            if (newval &&
                !xml_strcmp(newval_val, (const xmlChar *)"not-supported"))  {

                log_info("\n\n Value is not supported for %s editop, host-name=%s",
                         op_editop_name(editop),
                         newval_val);

                /* Validation failed if a value is not supported */
                res = ERR_NCX_OPERATION_NOT_SUPPORTED;
            } else {
                /* Run device instrumentation here  */
            }

For example, if a client send the edit to create /system/host-name with value not equal to "not-supported" the server will write and apply this edit with out any errors. However, if the value will be "not-supported" the server will start a rollback and reject this edit. The logging output may look as follows:


***** start commit phase on running for session 3, transaction 19 *****

Start full commit of transaction 19: 1 edit on running config
Start invoking commit SIL callback for create on example:system
Enter example_system_edit callback for commit phase
Enter u_example_system_edit callback for commit phase
Finished invoking user callback on example:system
Start invoking commit SIL callback for create on example:host-name
Enter example_system_host_name_edit callback for commit phase
Enter u_example_system_host_name_edit callback for commit phase

 Value is not supported for create editop, host-name=not-supported

agt_record_error for session 3: error-path object: <example:host-name>
commit user callback failed (operation not supported) for create on example:host-name

*** Halting commit 19: example:system SIL returned error ***


Start full rollback of transaction 19: 1 edit on running config
Rollback transaction 19, create edit on example:system
agt_val: Start Rollback SIL for nested_silcall obj 'host-name'
Skip rollback SIL callback; error node 'example:host-name' (operation not supported)
agt_val: Done Rollback SIL for nested_silcall obj 'host-name'
agt_val: Start Rollback SIL for nested_silcall obj 'system'
agt_val: Done Rollback SIL for nested_silcall obj 'system'
Reverse transaction 19, create edit on example:system
agt_val: Start reverse edit for system
Create dummy undo record for callbacks
Checking for validate user callback for delete edit on example:system
Found validate user callback for delete:example:system
agt_cfg: use undo Q for 'system'
agt_cfg: add undo nested_silcall for 'example:system'
Start invoking validate SIL callback for delete on example:system
Enter example_system_edit callback for validate phase
Enter u_example_system_edit callback for validate phase
Finished invoking user callback on example:system
Start invoking apply SIL callback for delete on example:system
Enter example_system_edit callback for apply phase
Enter u_example_system_edit callback for apply phase
Finished invoking user callback on example:system
Start invoking commit SIL callback for delete on example:system
Enter example_system_edit callback for commit phase
Enter u_example_system_edit callback for commit phase
Finished invoking user callback on example:system
agt_cb: Enter run_rollback_complete
agt_rpc: sending error <rpc-reply> for ses 3 msg '3'

ses_msg: send 1.1 buff:625 for s:3

trace_buff:

#615
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply message-id="3" xmlns:ex="http://netconfcentral.org/ns/example"
 xmlns:ncx="http://netconfcentral.org/ns/yuma-ncx"
 xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
 <rpc-error>
  <error-type>application</error-type>
  <error-tag>operation-not-supported</error-tag>
  <error-severity>error</error-severity>
  <error-app-tag>no-support</error-app-tag>
  <error-path>/ex:system/ex:host-name</error-path>
  <error-message xml:lang="en">operation not supported</error-message>
  <error-info>
   <error-number>273</error-number>
  </error-info>
 </rpc-error>
</rpc-reply>
agt_cb: Enter run_command_complete
agt_cb: Enter run_trans_complete
Clearing current txid for running config
agt_top: end dispatch yuma-netconf:rpc
ses_msg: free msg 0x7f225c000b20 for session 3


Note, that this edit will still be okay during Candidate Phase. The candidate datastore is a scratch pad for configurations and does not have to be completely valid all the time. But after you send <commit> operation to the server, it will run full set of validations and SIL callbacks that may rejects the edit, as for example we did in this example.