Let us go through simple examples that will illustrate how to use val value tree and how to navigate up and down in this tree. First we need a YANG module. Consider attached YANG module, simplified, but functional, example. You can download this YANG module from attachments and run make_sil_dir_pro to auto-generate stub SIL code for this module. And then refer to the attached modified SIL code for the same module that is used to exemplify all the functionality described in this article.


SIL code Generation command that was used in this example. Please refer to the attached SIL code.


> make_sil_dir_pro example --sil-get2 sil-edit2


module example {
  namespace "http://netconfcentral.org/ns/example";
  prefix "ex";

  revision "2020-01-01" {
    description "Example module.";
  }

  container top-cont {
    presence true;

    list list1 {
      key k1;

      leaf k1 { type int32; }
      leaf A { type string; }

      list inner {
        key k2;

        leaf k2 { type int32; }
        leaf B { type boolean; }
        leaf C { type string; }
      }
    }

    leaf-list ll-inner {
      type int32;
    }
  }
}


Assume we registered EDI2 callback for the “top-cont”, "list1" and "inner" nodes. Thus, whenever these nodes are edited, the EDIT2 callback function will be called and additional specific data can be validated.

In this example, we will validate a leaf "A" in the "list1" list when the MERGE operation is performed on "inner" list. The callback function may look as follows:


/********************************************************************
* FUNCTION example_edit2_cb
*
* Edit database object callback
*
* Path: list /top-cont
* Path: list /top-cont/list1
* Path: list /top-cont/list1/inner
*
* Add object instrumentation in COMMIT phase.
*
* INPUTS:
*     see agt/agt_cb.h for details
*
* RETURNS:
*     error status
********************************************************************/
static status_t
    example_edit2_cb (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;
    val_value_t *errorval = (curval) ? curval : newval;
    uint32 cur_errs = agt_get_error_count(msg);

    print_callback_info(errorval, cbtyp, editop, (const xmlChar *)"EDIT-2");

    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_MERGE:
            res = handle_merge(msg, newval, curval);
            break;
        case OP_EDITOP_REPLACE:
            break;
        case OP_EDITOP_CREATE:
            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);
    }

    if ((res != NO_ERR) && (cur_errs == agt_get_error_count(msg))) {
        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;

} /* example_edit2_cb */



Now let us take a loop in more details in the function handle_merge() that is used in this example to perform the following tasks:

  • Dump children values during MERGE operation
  • Print XPath representation of desired nodes
  • Navigate up and down in the val value tree and run simple validation


/********************************************************************
* FUNCTION handle_merge
*
* Dump children values during MERGE operation
* Print XPATH representation
* Navigate up and down in the val value tree and run simple validation
*
* RETURNS:
*   status, NO_ERR if successful
*
********************************************************************/
static status_t
    handle_merge (rpc_msg_t *msg,
                  val_value_t *newval,
                  val_value_t *curval)
{
    if (LOGDEBUG2) {
        log_debug("\n  Merge parameters:");
    }

    status_t res = NO_ERR;

    /* the edit is not really on this node; need to get
     * each child_undo record to get the real edited nodes
     */
    agt_cfg_transaction_t *txcb = RPC_MSG_TXCB(msg);
    agt_cfg_undo_rec_t *child_edit =
        agt_cfg_first_child_edit(txcb, newval, curval);

    while (child_edit && res == NO_ERR) {

        op_editop_t child_editop = OP_EDITOP_NONE;
        val_value_t *child_newval = NULL;
        val_value_t *child_curval = NULL;
        xmlChar *newval_str = NULL;
        xmlChar *curval_str = NULL;

        agt_cfg_child_edit_fields(child_edit,
                                  &child_editop,
                                  &child_newval,
                                  &child_curval);

        if (child_newval) {
            newval_str = val_make_sprintf_string(child_newval);
            if (!newval_str) {
                return ERR_INTERNAL_MEM;
            }
        }

        if (child_curval) {
            curval_str = val_make_sprintf_string(child_curval);
            if (!curval_str) {
                if (newval_str) {
                    m__free(newval_str);
                }
                return ERR_INTERNAL_MEM;
            }
        }

        if (LOGDEBUG2) {
            log_debug2("\n    %s: editop=%s newval=%s curval=%s",
                       child_newval ? VAL_NAME(child_newval) : NCX_EL_NULL,
                       child_editop ? op_editop_name(child_editop) : NCX_EL_NONE,
                       child_newval ? newval_str : NCX_EL_NULL,
                       child_curval ? curval_str : NCX_EL_NULL);
        }

        xmlChar *buff = NULL;
        val_value_t *logval = child_newval ? child_newval : child_curval;
        if (logval) {

            /* Generate XPATH full path for this child */
            res =
                val_gen_instance_id(NULL,
                                    logval,
                                    NCX_IFMT_XPATH1,
                                    &buff);
            if (res == NO_ERR && LOGDEBUG2) {
                log_debug2("\n    Child '%s' XPATH: %s",
                           VAL_NAME(logval),
                           buff);
            }
        }

        if (child_newval && val_set_by_default(child_newval)) {
            if (LOGDEBUG2) {
                log_debug2("\n        NEWVAL is default");
            }
        }

        if (child_curval && val_set_by_default(child_curval)) {
            if (LOGDEBUG2) {
                log_debug2("\n        CURVAL is default");
            }
        }

        res = navigate_in_tree(logval);
        if (res != NO_ERR) {
            /* report an error */
        }

        m__free(buff);
        m__free(newval_str);
        m__free(curval_str);

        child_edit = agt_cfg_next_child_edit(child_edit);
    }

    return res;

} /* handle_merge */


Now let us go through the function navigate_in_tree() that is used in this example to perform the following tasks:

  • Navigate up to a specific parent node
  • Find a specific child of just found parent
  • Run simple validation on the found node
  • Dump Xpath representation of the val value node
  • Log an error if special validation criteria are not met
  • Return an error to terminate the operation


/********************************************************************
* FUNCTION navigate_in_tree
*
* This function perform the following tasks:
*
*   - Navigate up to a specific parent node
*   - Find a specific child of just found parent
*   - Run simple validation on the found node
*   - Dump Xpath representation of the val value node
*   - Log an error if special validation criteria are not met
*   - Return an error to terminate the operation
*
********************************************************************/
static status_t
    navigate_in_tree (val_value_t *useval)
{

    status_t res = NO_ERR;
    val_value_t *parent = useval->parent;

    /* The following loop will go up until the top Root is reached */
    while (parent) {

        if (obj_is_root(VAL_OBJ(parent))) {
            /* reached root */
            parent = NULL;
            break;
        } else {
            parent = parent->parent;
        }

        /* find the right parent */
        if (!xml_strcmp(VAL_NAME(parent), (const xmlChar *)"list1")) {

            /* now find desired child of this node */
            val_value_t *chval = val_get_first_child(parent);
            for ( ; chval; chval = val_get_next_child(chval)) {
                /* Refer to val.h for more API to get the first
                 * child node. For example:
                 *
                 *  - val_get_first_terminal_child()
                 *  - val_find_child()
                 *  - val_find_next_child()
                 * etc.
                 */

                if (!xml_strcmp(VAL_NAME(chval), (const xmlChar *)"A")) {
                    /* check if this child value is OK to use
                     * and generate an error if it is not acceptable
                     */

                    xmlChar *use_str = val_make_sprintf_string(chval);
                    if (!use_str) {
                        return ERR_INTERNAL_MEM;
                    }

                    /* generate an error if the value of a child os not valid */
                    if (!xml_strcmp(use_str, (const xmlChar *)"not-valid")) {

                        xmlChar *buff = NULL;
                        res =
                            val_gen_instance_id(NULL,
                                                chval,
                                                NCX_IFMT_XPATH1,
                                                &buff);
                        if (res == NO_ERR && LOGDEBUG2) {
                            log_debug2("\n    Not supported! Value of:"
                                       "\n    %sis '%s'",
                                       buff,
                                       use_str);
                        }

                        m__free(buff);
                        m__free(use_str);
                        return ERR_NCX_OPERATION_NOT_SUPPORTED;
                    }

                    m__free(use_str);
                }
            }
        }
    }

    return res;

} /* navigate_in_tree */


As a result the MERGE edit on some inner leaf node can terminate the whole edit-config operation based on some special criteria.

Assume we already have the running datastore set-up and it looks as follows:


  data {
    top-cont {
      list1  1 {
        k1 1
        A not-valid
        inner  2 {
          k2 2
          B true
          C valid
        }
      }
    }
  }


Now we send the edit-config request that will trigger a MERGE on the "inner" list. The edit-config may look as follows:


<edit-config>
    <target>
    <candidate/>
    </target>
    <default-operation>merge</default-operation>
    <test-option>set</test-option>
    <config>
    <top-cont xmlns="http://netconfcentral.org/ns/example">
      <list1>
        <k1>1</k1>
        <A>not-valid</A>
        <inner>
          <k2>2</k2>
          <B>false</B>
          <C>also-valid</C>
        </inner>
      </list1>
    </top-cont>
  </config>
</edit-config>


As a result the server will call the above validation functions and check the parent nodes and their specific children and based on the previous snippets the server may reply as follows:


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

Start full commit of transaction 3921: 1 edit on running config
Start invoking commit SIL callback for merge on example:inner
 (inner) Enter EDIT-2 callback
 ------PHASE:commit;EDITOP:merge

  Merge parameters:
    B: editop=replace newval=false curval=true
    Child 'B' XPATH: /ex:top-cont/ex:list1[ex:k1='1']/ex:inner[ex:k2='2']/ex:B
    Not supported! Value of:
    /ex:top-cont/ex:list1[ex:k1='1']/ex:A is 'not-valid'
agt_record_error for session 3: error-path object: <example:inner>

commit user callback failed (operation not supported) for merge on example:inner

*** Halting commit 3921: example:inner SIL returned error ***


Start full rollback of transaction 3921: 1 edit on running config
Rollback transaction 3921, replace edit on example:C
Rollback transaction 3921, replace edit on example:B
Skip rollback SIL callback; error node 'example:inner' (operation not supported)
agt_cb: Enter run_rollback_complete
agt_rpc: sending error <rpc-reply> for ses 3 msg '4'

ses_msg: send 1.1 buff:654 for s:3

trace_buff:

#644
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply message-id="4" 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:top-cont/ex:list1[ex:k1='1']/ex:inner[ex:k2='2']</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_audit: skip audit record for RPC summary



Refer to the attached SIL code for complete SIL code example.