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'