If an application needs to perform additional actions and validation in the same transaction during <commit> operation, there are mechanisms implemented in the form of API functions called

  • SIL-SA Validate Complete callback
  • SIL-SA Apply Complete callback
  • SIL-SA Commit Complete callback
  • SIL-SA Rollback Complete callback


These callbacks are called Commit Completeness callbacks and they are In-Transaction type callbacks.


Manipulation with configuration data are only allowed for Set Hook callbacks. If  Commit Completeness callbacks are trying to adjust configuration data via sil_sa_add_edit() callback, the operation will be skipped.


The Commit Completeness callbacks will not be invoked in case the server is run with active:writable:running capability.


The Commit Completeness callbacks are NOT part of the yangdump-sdk code generation. After the make_sil_sa_dir_pro script is run, callbacks will not be generated automatically. All the In-Transaction callbacks are optional and merely intend to provide more efficient way to manipulate with the database and provide an access to the database at the specific phase, transaction, and edit.



The following function template definitions are used for Commit Completeness callback functions:


/* Typedef of the SIL-SA validate_complete callback */
typedef status_t
    (*agt_cb_sa_validate_complete_t) (const xmlChar *transaction_id);


/* Typedef of the SIL-SA apply_complete callback */
typedef status_t
    (*agt_cb_sa_apply_complete_t) (const xmlChar *transaction_id);

/* Typedef of the SIL-SA commit_complete callback */
typedef status_t
    (*agt_cb_sa_commit_complete_t) (const xmlChar *transaction_id,
                                    agt_commit_type_t commit_type);

/* Typedef of the SIL-SA rollback_complete callback */
typedef status_t
    (*agt_cb_sa_rollback_complete_t) (const xmlChar *transaction_id);

The registration for Commit Completeness callbacks 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 registration of all the types of Commit Completeness callbacks may look as follows:


/******************************************************************** 
* FUNCTION interfaces_init
* 
* initialize the server instrumentation library.
* Initialization Phase 1
* 
*********************************************************************/ 
static status_t 
     interfaces_init (void) 
{ 
   ...

    /* register SIL-SA validate complete callback */ 
    res = 
        agt_cb_sa_validate_complete_register(hooks_validate_complete_cb);
    if (res != NO_ERR) { 
        return res; 
    }

   ...

    /* register SIL-SA Apply Complete callback */ 
    res = 
        agt_cb_sa_apply_complete_register(hooks_apply_complete_cb);
    if (res != NO_ERR) { 
        return res; 
    }

   ...

    /* register SIL-SA Commit Complete callback */ 
    res =
        agt_cb_sa_commit_complete_register(hooks_commit_complete_cb);
    if (res != NO_ERR) { 
        return res; 
    }

   ...

    /* register rollback complete callback */ 
    res = 
        agt_cb_sa_rollback_complete_register(hooks_rollback_complete_cb); 
    if (res != NO_ERR) { 
        return res; 
    }

   ...
}


Now after the registration, whenever the specific Phase is done for the <commit> operation, the callback function will be called and provide the access to datastores with help of Get Data API. If there are multiple callbacks registered (multiple callbacks of the same type), all the registered callbacks will be called one after another right after the specific Phase is done during the <commit> operation.

The following example code illustrates how the specific Commit Completeness callback can be cleaned up. The callbacks clean up is getting done during Cleanup Phase.


/******************************************************************** 
* FUNCTION  interfaces_cleanup
*    cleanup the server instrumentation library 
* 
********************************************************************/ 
void interfaces_cleanup (void)
{
  ...
    /* Unregister SIL-SA Validate Complete callback */ 
    agt_cb_sa_validate_complete_unregister(hooks_validate_complete_cb);

  ...

    /* Unregister SIL-SA Apply Complete callback */ 
    agt_cb_sa_apply_complete_unregister(hooks_apply_complete_cb); 

  ...

    /* Unregister SIL-SA Commit Complete callback */ 
    agt_cb_sa_commit_complete_unregister(hooks_commit_complete_cb);

  ...

    /* Unregister Rollback Complete callback */ 
    agt_cb_rollback_complete_unregister(rollback_compl_callback); 

  ...
}



The SIL-SA version of the Commit Completeness functions are the user/system callbacks that are invoked when the specific Phase has been processed during the <commit> operation. If the callback fails the status of the failing callback is returned immediately and no further callbacks are made. As a result, the server will abort the commit.


The callbacks are object independent and module independent which means you don't have to link it to a specific object as it is done for EDIT or GET callbacks. The callbacks are intended to provide validation point after the corresponding Phase is completed. For SIL-SA usage the server does not provide running and candidate values, instead it passes the transaction ID for reference and Get Data API can be used to run additional validation if required.




Commit Completeness Callback Function Examples


Let us go through simple examples that will illustrate how to utilize SIL-SA Commit Completeness callbacks.

Consideration:

Consider that there are four modules loaded into the server and each having separate SIL code and you would like to register four different callbacks to be called when validation of respective module is completed on the commit.

When module number one is modified and a commit operation is performed, only callback registered in module one's SIL code should be called. In this way each module owner can write independent callback for his module, without side effects to other module callbacks.

Solution:

The SIL-SA Commit Completeness Callbacks are not regular SIL-SA callbacks, they cannot not be called based on the specific module or object or any other parameter. They can be named as “global” callbacks.

They can only be called after commit operation for the specific phase has finished not after the commit for the specific module or object is done.


The server has no control of identifying what module and when the specific edit is done for the specific module. The edit can be as complex as having multiple modules modifications at the same time. It may reference other modules as a leafref or augment, and those modules can also be triggered during the same edit.

In the considering scenario with four SIL-SA codes for four different modules you don't have to register four SIL-SA Commit Completeness callbacks for each module. You may register just one callback for each corresponding phase that will take care of the commit operation.

If you want to have a callback that will be invoked when a specific module or object is getting modified, then you should consider regular EDIT callbacks.



The following example code illustrates how the SIL-SA Validate Complete callback may look like


/********************************************************************
* FUNCTION hooks_validate_complete_cb
*
*  SIL-SA Validate Complete callback.
*
*
* RETURNS:
*   status
********************************************************************/
static status_t
    hooks_validate_complete_cb (const xmlChar *transaction_id_val)
{
    if (!transaction_id_val) {
        log_error("\ntransaction_id value not set");
        return ERR_INTERNAL_VAL;
    }

    const xmlChar *user = sil_sa_get_username();
    const xmlChar *client_addr = sil_sa_get_client_addr();
    status_t res = NO_ERR;

    if (LOGDEBUG2) {
        log_debug2("\n\n********************************************");
        log_debug2("\nEnter hooks_validate_complete_cb callback");
        log_debug2("\ntransaction_id -- %s", transaction_id_val);
        log_debug2("\nuser_id -- %s", user);
        log_debug2("\nclient_addr -- %s", client_addr);
        log_debug2("\n********************************************\n\n");
    }

    return res;

}  /* hooks_validate_complete_cb */


Assuming there are multiple modules loaded and many modules are modified. Now, when a commit command triggers the Validate Complete Callback and it is getting invoked it means that all validations for all the modules and all edits are completed at this point. Similarly appropriate callbacks are called for all phases - when commit operation for that phase is finished for all modules and edits.



The following example code illustrates how the SIL-SA Apply Complete callback may look like.


/********************************************************************
* FUNCTION hooks_apply_complete_cb
*
*  SIL-SA Apply Complete callback.
*
*
* RETURNS:
*   status
********************************************************************/
static status_t
    hooks_apply_complete_cb (const xmlChar *transaction_id_val)
{
    if (!transaction_id_val) {
        log_error("\ntransaction_id value not set");
        return ERR_INTERNAL_VAL;
    }

    const xmlChar *user = sil_sa_get_username();
    const xmlChar *client_addr = sil_sa_get_client_addr();
    status_t res = NO_ERR;

    if (LOGDEBUG2) {
        log_debug2("\n\n********************************************");
        log_debug2("\nEnter hooks_apply_complete_cb callback");
        log_debug2("\ntransaction_id -- %s", transaction_id_val);
        log_debug2("\nuser_id -- %s", user);
        log_debug2("\nclient_addr -- %s", client_addr);
        log_debug2("\n********************************************\n\n");
    }

    return res;

}  /* hooks_apply_complete_cb */


The following example code illustrates how the SIL-SA Commit Complete callback may look like.


/********************************************************************
* FUNCTION hooks_commit_complete_cb
*
*  SIL-SA Commit Complete callback.
*
*
* RETURNS:
*   status
********************************************************************/
static status_t
    hooks_commit_complete_cb (const xmlChar *transaction_id_val,
                              agt_commit_type_t commit_type)
{
    if (!transaction_id_val) {
        log_error("\ntransaction_id value not set");
        return ERR_INTERNAL_VAL;
    }

    const xmlChar *user = sil_sa_get_username();
    const xmlChar *client_addr = sil_sa_get_client_addr();
    const xmlChar *type = agt_commit_complete_get_type(commit_type);
    status_t res = NO_ERR;
    status_t res2 = NO_ERR;

    if (commit_type == AGT_COMMIT_TYPE_REPLAY) {
        return res;
    }

    if (LOGDEBUG2) {
        log_debug2("\n\n********************************************");
        log_debug2("\nEnter hooks_commit_complete_cb callback");
        log_debug2("\ntransaction_id -- %s", transaction_id_val);
        log_debug2("\nuser_id -- %s", user);
        log_debug2("\nclient_addr -- %s", client_addr);
        log_debug2("\ncommit type -- %s", type);
    }

    const xmlChar *defpath =
        (const xmlChar *)"/testsystem";

    val_value_t *testval =
        sil_sa_get_data(NCX_CFGID_CANDIDATE,
                        defpath,
                        &res2);
    if (testval) {
        log_info("\n+++ Got testval %s (%s)",
                 VAL_NAME(testval),
                 get_error_string(res));

        val_value_t *child = val_get_first_child(testval);
        if (child) {
            if (child->btyp && typ_is_simple(child->btyp)) {
                xmlChar *valstr = val_make_sprintf_string(child);
                if (valstr == NULL) {
                    return ERR_INTERNAL_MEM;
                }

                log_debug("\nchild:   %s='%s'",
                          VAL_NAME(child),
                          valstr);

                /* FORCE ERROR */
                if (!xml_strcmp(valstr,
                        (const xmlChar *)"commit-completeness-test1")) {

                    res = ERR_NCX_OPERATION_NOT_SUPPORTED;
                    log_info("\n------ REPORTING AN ERROR (%s)\n",
                        get_error_string(res));
                }

                m__free(valstr);
            }
        }
    }

    if (LOGDEBUG2) {
        log_debug2("\n********************************************\n\n");
    }

    return res;

}  /* hooks_commit_complete_cb */

The following example code illustrates how the SIL-SA Rollback Complete callback may look like.


/********************************************************************
* FUNCTION hooks_rollback_complete_cb
*
*  SIL-SA Rollback Complete callback.
*
*
* RETURNS:
*   status
********************************************************************/
static status_t
    hooks_rollback_complete_cb (const xmlChar *transaction_id_val)
{
    if (!transaction_id_val) {
        log_error("\ntransaction_id value not set");
        return ERR_INTERNAL_VAL;
    }

    const xmlChar *user = sil_sa_get_username();
    const xmlChar *client_addr = sil_sa_get_client_addr();
    status_t res = NO_ERR;

    if (LOGDEBUG2) {
        log_debug2("\n\n********************************************");
        log_debug2("\nEnter hooks_rollback_complete_cb callback");
        log_debug2("\ntransaction_id -- %s", transaction_id_val);
        log_debug2("\nuser_id -- %s", user);
        log_debug2("\nclient_addr -- %s", client_addr);
        log_debug2("\n********************************************\n\n");
    }

    return res;

}  /* hooks_rollback_complete_cb */


In the examples above, the callback simply logs all the available parameters and denies the transaction if a specific node exist in the Candidate datastore (in case of Commit Complete Callback).
In this callbacks, there could be handling for customer specific pointers, it is a good place to initialize specific pointers that will be cleaned after the specific Phase if done during <commit> operation.


In the example, we used two additional APIs to retrieve current transaction “user name” and “address”: 


  /* Get the user_id value from the message header */ 
  const xmlChar *user = sil_sa_get_username();

  /* Get the client address (client_addr value from the message header) */
  const xmlChar *client_addr = sil_sa_get_client_addr();


These two APIs only available in the SIL-SA version of the SIL code and intended to provide an access to specific fields in the message header since the message header itself is not available in the SIL-SA version of of the SIL.


  /* Get the commit type string */
  const xmlChar *type = agt_commit_complete_get_type(commit_type);

This API is only available in the SIL-SA version of the SIL code and intended to provide a string value of the commit type parameter and intended for logging.