AVAILABLE SINCE 20.10-0 RELEASE
A Transaction Hook is a callback function that is invoked within the transaction when an object is modified to provide one more level of validation or an extra action. The Transaction Hook is similar to the Set Hook except this callback is invoked just before the data is finally committed to the running datastore. This callback will be invoked after EDIT callbacks are all done. Note that the Transaction Hook is not allowed to change anything, It is not allowed to call sil_sa_add_edit API, or to alter the edits or the datastore in any way.
Manipulation with datastore are only allowed for Set Hook callbacks. If Transaction Hook callbacks call sil_sa_add_edit API, the operation will be ignored. Do not return error just ignore add_edit calls.
The following function template definition is used for the Transaction Hook callback functions:
/* Typedef of the agt_cb_sa_hook_t callback */
typedef status_t
(*agt_cb_sa_hook_t) (ses_cb_t *scb,
rpc_msg_t *msg,
op_editop_t editop,
val_value_t *newval,
val_value_t *curval,
const xmlChar *transaction_id,
boolean isvalidate,
boolean isload,
boolean isrunning);
scb | session control block making the request |
msg | incoming rpc_msg_t in progress |
editop | edit operation enumeration for the node being edited |
newval | value holding the proposed changes to apply to the current config, depending on the editop value. |
curval | current values from the <running> or <candidate> configuration, if any. Could be NULL for create and other operations. |
transaction_id | transaction ID of the transaction control block in progress |
isvalidate | TRUE if this Transaction is for <validate> operation |
isload | TRUE if this Transaction is for a Load operation |
isrunning | TRUE if this Transaction is for the the running datastore |
The Transaction Hook callback function is hooked into the server the same way as Set Hook callback with the agt_cb_sa_hook_register function, described below. The agt_cb_hook_register function is used to declare the callback as a specific style callback. 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.
extern status_t agt_cb_sa_hook_register (const xmlChar *defpath, agt_hook_fmt_t format, agt_hook_type_t type, agt_cb_sa_hook_t cbfn)
defpath | Absolute path expression string indicating which node the callback function is for. |
format | different hook formats dictates specific hook functionality |
type | different hook types dictates hook invocation point |
cbfn | address of SIL-SA callback function to use |
The format parameter is important when you want to specify how Transaction Hook callbacks will be invoked. There are two options available for this parameter:
AGT_HOOKFMT_NODE: Set the type of the callback to this value if You want to make sure that the callback will be invoked only when you modify the node that registered the callback but not any of its children.
AGT_HOOKFMT_SUBTREE: If the format is set to this value, the callback will be invoked if you edit children as well.
The type parameter is important when you want to set the type of callback. There are two options available for this parameter, either Set Hook or Transaction Hook callback. In case you want to register Transaction Hook callback select corresponding type of the callback:
AGT_HOOK_TYPE_SETHOOK: Set the type of the callback to this value if You want to register Set Hook callback.
AGT_HOOK_TYPE_TRANSACTION: Set the type of the callback to this value if You want to register Transaction Hook callback.
The following example code illustrates how hook-based callbacks can be cleaned up. The callbacks cleanup is getting done during module Cleanup Phase. Where defpath is an absolute path expression string indicating which node the callback function is for.
extern void
agt_cb_sa_hook_unregister (const xmlChar *defpath);
EXAMPLES
The Transaction Hook can be set to specific object at run-time with a callback as follows. Assume we have a leaf node "example" in the YANG module. Register the Transaction Hook to the /example node of the example YANG module.
/********************************************************************
* FUNCTION example_init
*
* initialize the server instrumentation library.
* Initialization Phase 1
*
*********************************************************************/
static status_t
example_init (void)
{
...
/* Register an object specific Transaction Hook callback */
res =
agt_cb_sa_hook_register((const xmlChar *)"/example",
AGT_HOOKFMT_NODE,
AGT_HOOK_TYPE_TRANSACTION,
hook_cb);
...
}
Now, whenever the /example node is edited, the Transaction Hook callback will be invoked and it will perform specific validation actions. The callback function may look as follows:
/******************************************************************** * FUNCTION hook_cb * * Callback function for server object handler * Used to provide a callback for a specific named object * * Transaction-Hook: * trigger: edit /example * effect: * - if testval node exist the DELETE operation will be denied * - if testval is false, the operation will be denied * * INPUTS: * scb == session control block making the request * msg == incoming rpc_msg_t 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. * transaction_id == transaction ID of the transaction control block in progress * isvalidate == TRUE if this Transaction is for <validate> operation * isload == TRUE if this Transaction is for a Load operation * isrunning == TRUE if this Transaction is for the the running datastore * * RETURNS: * status ********************************************************************/ static status_t hook_cb (ses_cb_t *scb, rpc_msg_t *msg, op_editop_t editop, val_value_t *newval, val_value_t *curval, const xmlChar *transaction_id, boolean isvalidate, boolean isload, boolean isrunning) { status_t res = NO_ERR; status_t res2 = NO_ERR; val_value_t *errorval = (curval) ? curval : newval; const xmlChar *user = sil_sa_get_username(); const xmlChar *client_addr = sil_sa_get_client_addr(); if (LOGDEBUG2) { log_debug2("\n\n********************************************"); log_debug2("\nEnter hooks_sethook_edit callback for silsa-test " "---- 1"); log_debug2("\ntransaction_id -- %s", transaction_id); log_debug2("\nuser_id -- %s", user); log_debug2("\nclient_addr -- %s", client_addr); log_debug2("\nisvalidate -- %s", isvalidate ? NCX_EL_TRUE : NCX_EL_FALSE); log_debug2("\nisload -- %s", isload ? NCX_EL_TRUE : NCX_EL_FALSE); log_debug2("\nisrunning -- %s", isrunning ? NCX_EL_TRUE : NCX_EL_FALSE); log_debug2("\n********************************************\n\n"); } /* get the node that want to validate */ val_value_t *testval = sil_sa_get_data(NCX_CFGID_RUNNING, (const xmlChar *)"/if:interfaces/if:interface[if:name]/if:enabled", &res2); switch (editop) { case OP_EDITOP_LOAD: break; case OP_EDITOP_MERGE: case OP_EDITOP_REPLACE: case OP_EDITOP_CREATE: if (testval) { res = ERR_NCX_ACCESS_DENIED; } break; case OP_EDITOP_DELETE: if (testval && VAL_BOOL(testval)) { res = ERR_NCX_ACCESS_DENIED; } else { res2 = NO_ERR; } 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; } /* hook_cb */
So whenever some north bound agent edit the /example node the callback kicks in and additionally validates the /interfaces/interface[name]/enabled node.