XPath Callback
Note
This callback is available starting in version 22.10T-8.
The XPath Boolean EVAL Callback is used in netconfd-pro to allow SIL code to handle XPath processing instead of using the internal XPath engine to process the XPath evaluation.
A SIL callback function for XPath Boolean Evaluation can be registered and used, similar to an EDIT2 Callback. It is responsible for providing the boolean result for a 'must' or 'when' expression evaluation.
The callback can choose to handle the expression evaluation, or skip it, in which case the server will process the evaluation with the XPath engine.
Often it is much faster for SIL code to maintain internal state or to validate the requirements of a 'must' expression during the SIL callback edit phases. In either case, edit transaction performance can be greatly improved with proper use of this callback function.
Callback Overview
Register an XPath callback for a specific YANG configuration data node.
The target object for the callback will be the object for the context node in the 'must' or 'when' XPath evaluation.
Only 'must' and 'when' evaluation done during datastore validation is affected.
When any 'must' or 'when' expression needs evaluation for a specific data node instance, the XPath callback will be checked.
The callback may need to check the XPath expression string if more than one 'must' or 'when' statement is defined for the object.
The callback returns ERR_NCX_SKIPPED if the expression is not supported by the callback.
The callback uses the config data tree and possibly internal system state to determine the key leafs for the evaluation.
The callback uses the config data tree and possibly internal system state to determine the boolean result for the evaluation.
Restrictions
The XPath callback is extremely restricted in the APIs that it can use:
No XPath evaluation
No "get_data" APIs
No alteration of any server data
No alteration of the transaction in progress
XPath Boolean EVAL Callback
The following function template definition is used for XPath 'must' and 'when' statement evaluation
-
typedef status_t (*xpath_bool_eval_fn_t)(struct xpath_pcb_t_ *pcb, val_value_t *context, val_value_t *docroot, boolean *result)
XPath Boolean EVAL Replacement Callback.
Callback to implement the XPath semantics for a must or when statement for a datastore object.
Use XPATH_EXPRSTR(pcb) to examine the XPath expression The callback must return ERR_NCX_SKIPPED if the expression evaluation is skipped.
Only supported for used during datastore processing to access configuration nodes (val_value_t tree) Support for GET2 callbacks not supported at this time
Used in the server to optimize datastore validation and delete_dead_nodes when-stmt processing.
- Param pcb:
XPath parser control block in use
- Param context:
context value node to use. The object of this node contains a pointer to this callback function
- Param docroot:
document root value node to use.
- Param result:
[out] address of return result if NO_ERR
*result is TRUE if the must/when result is true
*result is FALSE if the must/when result is false
- Retval NO_ERR:
if test is done and *result is valid
- Retval ERR_NCX_SKIPPED:
if test is not done and *result is not valid. Actual XPath eval will be done instead.
- Retval other:
error to force the XPath test to fail with an error and datastore operation will fail
- Return:
status
Force TRUE XPath Result
There is a utility XPath callback function in agt/agt_util.h
that can be used to return 'true' for the XPath evaluation callback.
Note
The 'agt_xpath_force_true' function has the same effect as removing the 'must' or 'when' statement from the YANG module. Use with extreme caution.
This API should only be used if the internal instrumentation code will handle all 'must' and 'when' evaluation for the object, and all such evaluations should always return 'true'.
-
status_t agt_xpath_force_true(struct xpath_pcb_t_ *pcb, val_value_t *context, val_value_t *docroot, boolean *result)
XPath Boolean EVAL Replacement Callback.
Callback to implement the XPath semantics for a must or when statement for a datastore object.
Use XPATH_EXPRSTR(pcb) to examine the XPath expression The callback must return ERR_NCX_SKIPPED if the expression evaluation is skipped.
Only supported for used during datastore processing to access configuration nodes (val_value_t tree) Support for GET2 callbacks not supported at this time
Used in the server to optimize datastore validation and delete_dead_nodes when-stmt processing.
- Parameters:
pcb -- XPath parser control block in use
context -- context value node to use. The object of this node contains a pointer to this callback function
docroot -- document root value node to use.
result -- [out] address of return result if NO_ERR
*result is TRUE if the must/when result is true
*result is FALSE if the must/when result is false
- Return values:
NO_ERR -- if test is done and *result is valid
ERR_NCX_SKIPPED -- if test is not done and *result is not valid. Actual XPath eval will be done instead.
other -- error to force the XPath test to fail with an error and datastore operation will fail
- Returns:
status
XPath Callback Initialization and Cleanup
An XPath Boolean EVAL callback function is registered with the 'agt_cb_register_xpath_callback' function, described below.
The registration is done during the Initialization Phase 1 before the startup configuration is loaded into the running configuration database.
There is no special cleanup callback needed. The 'agt_cb_unregister_callbacks' function will unregister the XPath callback.
This callback can only be used in SIL code. SIL-SA code is not supported.
-
status_t agt_cb_register_xpath_callback(const xmlChar *modname, const xmlChar *defpath, const xmlChar *version, xpath_bool_eval_fn_t cbfn)
Register an object specific XPath callback function.
Use the same fn for all must/when stmts for the object Function must return ERR_NCX_SKIPPED if invoked for an unsupported expression.
- Parameters:
modname -- module that defines the target object for these callback functions
defpath -- Xpath with default (or no) prefixes defining the object that will get the callbacks
version --
exact module revision date expected if condition not met then an error will be logged (TBD: force unload of module!)
NULL means use any version of the module
cbfn -- address of callback function to use
- Returns:
status
The following example shows how the 'agt_xpath_force_true' function can be used for an object:
res = agt_cb_register_xpath_callback(
y_bbf_l2_fwd_M_bbf_l2_fwd,
(const xmlChar *)"/bbf-l2-fwd:forwarding/forwarders/forwarder/ports/port/sub-interface",
y_bbf_l2_fwd_R_bbf_l2_fwd,
agt_xpath_force_true);
if (res != NO_ERR) {
return res;
}
XPath Callback Example
In this example the XPath expression is checked to determine
if it should be skipped or not.
This function can be found in agt/agt_util.h
.
/**
* @brief XPath Boolean EVAL Replacement Callback
*
* Callback to implement the XPath semantics for a
* must or when statement for a datastore object.
*
* Use XPATH_EXPRSTR(pcb) to examine the XPath expression
* The callback must return ERR_NCX_SKIPPED if the expression
* evaluation is skipped.
*
* Only supported for used during datastore processing
* to access configuration nodes (val_value_t tree)
* Support for GET2 callbacks not supported at this time
*
* Used in the server to optimize datastore validation
* and delete_dead_nodes when-stmt processing.
*
* @param pcb XPath parser control block in use
* @param context context value node to use.
* The object of this node contains a pointer
* to this callback function
* @param docroot document root value node to use.
* @param[out] result address of return result if NO_ERR
* - *result is TRUE if the must/when result is true
* - *result is FALSE if the must/when result is false
* @return status
* @retval NO_ERR if test is done and *result is valid
* @retval ERR_NCX_SKIPPED if test is not done and
* *result is not valid. Actual XPath eval will be
* done instead.
* @retval other error to force the XPath test to fail
* with an error and datastore operation will fail
*/
status_t
agt_xpath_example (struct xpath_pcb_t_ *pcb,
val_value_t *context,
val_value_t *docroot,
boolean *result)
{
/* get the XPath expression
* There are many fields in the 'pcb' setup that
* could also be checked. See documentation
*/
const xmlChar *expr = XPATH_EXPRSTR(pcb);
/* the XPath expression that this callback handles */
const xmlChar *myexpr = (const xmlChar *)"/some/node = 5";
/* exit now if expression not the one expected */
if (xml_strcmp(expr, myexpr)) {
return ERR_NCX_SKIPPED;
}
/* If needed the context node and docroot can be
* traversed using val_child and val.h APIs
* This is a configuration validation check so
* only config=true nodes within the 'docroot' tree
* can be accessed.
*/
(void)context;
(void)docroot;
/* after processing the XPath data tree and/or internal
* data, the *result must be set and NO_ERR returned
*/
*result = TRUE;
return NO_ERR;
} /* agt_xpath_example */
Automatic Code Generation For XPath Callbacks
The ywx:sil-xpath-function extension can be used to have the SIL code generation (e.g., make_sil_dir_pro) handle the XPath callback code generation.
Using An Auto-Generated Callback
If the value default
is used then the SIL code will
generate:
a code stub for the XPath callback
the code to register the XPath callback
the code to unregister all callbacks for the data node, if no other callbacks registered for the node.
If the 'split' format for code generation is used then:
the 'user' C file will contain the XPath callback function definition
the 'user' H file will contain the XPath callback function declaration
the 'system' C file will contain the XPath callback registration code in the 'init' function
the 'system' C file will contain the XPath callback un-registration code in the 'cleanup' function
If the 'no-split' format for code generation is used then all functions will be in the C file.
Using An Existing Callback
If the value is not default
then it must be a valid C function name.
The SIL code will generate:
the code to register the XPath callback
the code to unregister all callbacks for the data node, if no other callbacks registered for the node.
If the function is 'agt_xpath_force_true` then no additional CLI parameters are needed for the code generation.
If the function is a custom callback, then the --sil-include CLI parameter should be used to allow the generated code to find this function declaration.
If the 'split' format for code generation is used then:
the 'system' C file will contain the XPath callback registration code in the 'init' function
the 'system' C file will contain the XPath callback un-registration code in the 'cleanup' function
If the 'no-split' format for code generation is used then all functions will be in the C file.
XPath Callback Example With Augments
The YANG 'augment' statement is often used together with the 'when' statement to add conditional nodes to another 'base' module. It is possible to add augmenting nodes to another augmentation instead of the base module, but that is not shown here.
The XPath expressions used here are simple for tutorial purposes. It is expected more complex expressions are used for XPath callback.
It is possible that system-specific information can be used to speed the expression evaluation.
Base Module
The base module has the target list /top/list1
.
The leaf 'col2' is only present if its sibling 'col1' is equal to
10
module test44base {
yang-version 1.1;
namespace "urn:yumaworks:params:xml:ns:yang:test44base";
prefix "t44base";
revision 2025-04-13 {
description "Initial revision.";
}
container top {
list list1 {
key idx;
leaf idx { type uint32; }
leaf col1 { type uint32; }
leaf col2 {
when "../col1 = 10";
type uint32;
}
}
}
}
Augment Module
The augmenting module is adding 2 leafs to the target list
The 'col3' and 'col4' leafs are only added if the target list child leaf 'col1' is equal to
9
.
module test44aug {
yang-version 1.1;
namespace "urn:yumaworks:params:xml:ns:yang:test44aug";
prefix "t44aug";
import test44base { prefix t44base; }
revision 2025-04-13 {
description "Initial revision.";
}
augment "/t44base:top/t44base:list1" {
when "t44base:col1 = 9";
leaf col3 { type uint32; }
leaf col4 { type uint32; }
}
}
Simple Bundle
A YANG bundle called test44
is created:
> make_sil_bundle test44 test44base test44aug
After this is done the 2 SIL instrumentation files can be edited.
Base Module: u_test44base.c
The when-stmt in the 'col2' leaf can be checked.
leaf col2 {
when "../col1 = 10";
type uint32;
}
XPath Callback for leaf 'col2'
/*
* @brief XPath Boolean EVAL Replacement Callback
*
* Callback to implement the XPath semantics for a
* must or when statement for a datastore object.
*
* Use XPATH_EXPRSTR(pcb) to examine the XPath expression
* The callback must return ERR_NCX_SKIPPED if the expression
* evaluation is skipped.
*
* Only supported for used during datastore processing
* to access configuration nodes (val_value_t tree)
* Support for GET2 callbacks not supported at this time
*
* Used in the server to optimize datastore validation
* and delete_dead_nodes when-stmt processing.
*
* @param pcb XPath parser control block in use
* @param context context value node to use.
* The object of this node contains a pointer
* to this callback function
* @param docroot document root value node to use.
* @param[out] result address of return result if NO_ERR
* - *result is TRUE if the must/when result is true
* - *result is FALSE if the must/when result is false
* @return status
* @retval NO_ERR if test is done and *result is valid
* @retval ERR_NCX_SKIPPED if test is not done and
* *result is not valid. Actual XPath eval will be
* done instead.
* @retval other error to force the XPath test to fail
* with an error and datastore operation will fail
*/
static status_t
bool_eval_fn (struct xpath_pcb_t_ *pcb,
val_value_t *context,
val_value_t *docroot,
boolean *result)
{
(void)docroot;
/* get the XPath expression
* There are many fields in the 'pcb' setup that
* could also be checked. See documentation
*/
const xmlChar *expr = XPATH_EXPRSTR(pcb);
/* exit now if expression not the one expected */
if (xml_strcmp(expr, COL2_EXPR)) {
return ERR_NCX_SKIPPED;
}
/* expecting parent to be list1 */
val_value_t *list1 = context->parent;
if (list1 != NULL) {
const xmlChar *modname = y_t44base_M_t44base;
const xmlChar *name = y_t44base_N_col1;
val_value_t *col1 = val_child_find(list1, modname, name);
if (col1 != NULL) {
*result = (VAL_UINT32(col1) == 10);
return NO_ERR;
}
} // else should not happen!
*result = false;
return NO_ERR;
}
The code that does the quick lookup of 'col1' leaf does the same work as the XPath engine but faster.
The context node is the 'col2' leaf so the parent node 'list1' is needed
The function 'val_child_find' is used to find 'col1'
The 'col1' leaf is type 'uint32' so the 'VAL_UINT32' macro is used to check its value
/* expecting parent to be list1 */
val_value_t *list1 = context->parent;
if (list1 != NULL) {
const xmlChar *modname = y_t44base_M_t44base;
const xmlChar *name = y_t44base_N_col1;
val_value_t *col1 = val_child_find(list1, modname, name);
if (col1 != NULL) {
*result = (VAL_UINT32(col1) == 10);
return NO_ERR;
}
} // else should not happen!
Initialization for bool_eval_fn Callback
The 'col2' details are defined in the COL2_PATH
and COL2_EXPR
constants:
/* target string for the col2 object for when-stmt callback */
#define COL2_PATH \
(const xmlChar *)"/t44base:top/t44base:list1/t44base:col2";
#define COL2_EXPR (const xmlChar *)"../col1 = 10"
The init function 'u_test4base_init' is modified to add the XPath callback initialization:
const xmlChar *defpath = COL2_PATH;
res = agt_cb_register_xpath_callback(modname,
defpath,
revision,
bool_eval_fn);
Augment Module: u_test44aug.c
The when-stmt in the 'augment' statement can be checked.
The XPath callback is installed for the parent 'list1'
The callback is invoked for each augmenting node ('col3' and 'col4')
augment "/t44base:top/t44base:list1" {
when "t44base:col1 = 9";
leaf col3 { type uint32; }
leaf col4 { type uint32; }
}
XPath Callback for list 'list1'
/*
* @brief XPath Boolean EVAL Replacement Callback
*
* Callback to implement the XPath semantics for a
* must or when statement for a datastore object.
*
* Use XPATH_EXPRSTR(pcb) to examine the XPath expression
* The callback must return ERR_NCX_SKIPPED if the expression
* evaluation is skipped.
*
* Only supported for used during datastore processing
* to access configuration nodes (val_value_t tree)
* Support for GET2 callbacks not supported at this time
*
* Used in the server to optimize datastore validation
* and delete_dead_nodes when-stmt processing.
*
* @param pcb XPath parser control block in use
* @param context context value node to use.
* The object of this node contains a pointer
* to this callback function
* @param docroot document root value node to use.
* @param[out] result address of return result if NO_ERR
* - *result is TRUE if the must/when result is true
* - *result is FALSE if the must/when result is false
* @return status
* @retval NO_ERR if test is done and *result is valid
* @retval ERR_NCX_SKIPPED if test is not done and
* *result is not valid. Actual XPath eval will be
* done instead.
* @retval other error to force the XPath test to fail
* with an error and datastore operation will fail
*/
static status_t
aug_eval_fn (struct xpath_pcb_t_ *pcb,
val_value_t *context,
val_value_t *docroot,
boolean *result)
{
(void)docroot;
/* get the XPath expression
* There are many fields in the 'pcb' setup that
* could also be checked. See documentation
*/
const xmlChar *expr = XPATH_EXPRSTR(pcb);
/* exit now if expression not the one expected */
if (xml_strcmp(expr, LIST1_EXPR)) {
return ERR_NCX_SKIPPED;
}
/* context is list1; find col1 child */
const xmlChar *modname = y_t44base_M_t44base;
const xmlChar *name = y_t44base_N_col1;
val_value_t *col1 = val_child_find(context, modname, name);
if (col1 != NULL) {
*result = (VAL_UINT32(col1) == 9);
return NO_ERR;
}
*result = false;
return NO_ERR;
}
The code that does the quick lookup of 'col1' leaf is similar to the base module but not the same.
The context node is the 'list1' parent list, not the 'col3' or 'col4' leafs. The XPath is invoked on behalf of each leaf, but the 'list1' parent is the context node in each case.
The function 'val_child_find' is used to find 'col1' but the 'module-name' field is for the base module, not the augmenting module.
The 'col1' leaf is type 'uint32' so the 'VAL_UINT32' macro is used to check its value
/* context is list1; find col1 child */
const xmlChar *modname = y_t44base_M_t44base;
const xmlChar *name = y_t44base_N_col1;
val_value_t *col1 = val_child_find(context, modname, name);
if (col1 != NULL) {
*result = (VAL_UINT32(col1) == 9);
return NO_ERR;
}
Initialization for aug_eval_fn Callback
The 'list1' details are defined in the LIST1_PATH
and LIST2_EXPR
constants:
/* target string for the col2 object for when-stmt callback */
#define LIST1_PATH \
(const xmlChar *)"/t44base:top/t44base:list1";
#define LIST1_EXPR (const xmlChar *)"t44base:col1 = 9"
The init function 'u_test4aug_init' is modified to add the XPath callback initialization:
const xmlChar *defpath = LIST1_PATH;
res = agt_cb_register_xpath_callback(modname,
defpath,
revision,
aug_eval_fn);
Example Edit Showing The XPath Callbacks
I this example, a 'list1' entry is created on the server.
After building and installing the 'test44' bundle, the server is started. E.g.
> netconfd-pro --log-level=debug4 --bundle=test44
The the following <edit-config> operation is sent to the server.
Entry '1' is created with the augmenting leafs 'col3' and 'col4'
<?xml version="1.0" encoding="UTF-8"?>
<rpc message-id="3"
xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<edit-config>
<target>
<candidate/>
</target>
<default-operation>merge</default-operation>
<test-option>set</test-option>
<config>
<top
xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"
nc:operation="merge"
xmlns="urn:yumaworks:params:xml:ns:yang:test44base">
<list1>
<idx>1</idx>
<col1>9</col1>
<col3 xmlns="urn:yumaworks:params:xml:ns:yang:test44aug">5</col3>
<col4 xmlns="urn:yumaworks:params:xml:ns:yang:test44aug">10</col4>
</list1>
</top>
</config>
</edit-config>
</rpc>
During the edit transaction for this request the server will check the new nodes with 'when' statements during a function called 'delete_dead_nodes2'.
apply_write_val[4]: apply create on /t44base:top
start delete_dead_nodes2 for transaction
delete_dead_nodes2: undo for test44base:top
start delete_dead_nodes for node 'test44base:top'
start delete_dead_nodes for node 'test44base:list1'
start delete_dead_nodes for node 'test44base:idx'
start delete_dead_nodes for node 'test44base:col1'
start delete_dead_nodes for node 'test44aug:col3'
agt_val: Start when-stmt-check for test44aug:col3
xpath1: Invoking Bool Eval Callback for list1
xpath1: Bool Eval Callback return: (ok)
val: when test 't44base:col1 = 9' true with context 'list1' for node 'col3'
when_chk: test passed for node 'test44aug:col3'
start delete_dead_nodes for node 'test44aug:col4'
agt_val: Start when-stmt-check for test44aug:col4
xpath1: Invoking Bool Eval Callback for list1
xpath1: Bool Eval Callback return: (ok)
val: when test 't44base:col1 = 9' true with context 'list1' for node 'col4'
when_chk: test passed for node 'test44aug:col4'