Updating SIL and SIL-SA Code

There is no automatic code generation at this time to update SIL or SIL-SA code if the YANG module is changed. This must be done manually with a text editor.

Note

In most cases, only one file (the User C File) needs to be edited, and all other files can simply be replaced with the auto-generated code for the new module revision.

  • Compare code generation between old and new versions

    • It is useful to compare the code generated for the current version with the code generated for the new version.

    • The individual files (e.g., y_rpc1.c or y_rpc1.h) can be compared in each code directory, after a 'make_sil' script has been run for each module version.

  • New code is not always needed when a YANG module is updated.

    • Changes to YANG validation, such as 'must', 'when' and 'unique' statements should not affect SIL or SIL-SA code generation. YANG validation is handled in the server, not by SIL or SIL-SA code.

    • Changes to metadata such as a YANG identity should not impact C file code, but may cause H file code to change slightly.

  • Changes to YANG features can impact the auto-generated code.

    • Complex changes that change the 'if-feature' wrapper code is not shown or discussed here. Comparison of the C or H files should identify the wrapper code that needs to be added or removed.

There are several module update examples in this section to explain this manual process. Simple YANG modules are used to show different types of object additions.

  • Any differences between SIL and SIL-SA code will be highlighted. otherwise the SIL-SA APIs are the same as the SIL version.

  • The current recommended (default) parameters are used for the make_sil_dir_pro or make_sil_sa_dir scripts.

  • Updates will designated as 'optional' or 'mandatory'. E.g.,

    • Adding a constant or struct typedef to an H file for a new data node is optional

    • Adding a registration function call for a new RPC operation is mandatory.

Updating RPC Operations

This section demonstrates different code changes related to RPC operations.

Adding Input and Output Parameters

One common update use-case is to add new input or output parameters to an existing RPC operation.

The current version of the module (rpc1.yang) has one RPC operation.

rpc test1 {
  input {
    leaf param1 { type string; }
    leaf param2 { type string; }
  }
  output {
    leaf result-message { type string; }
  }
}

The new version is adding a param3 leaf to the input and a result-count leaf to the output.

 rpc test1 {
   input {
     leaf param1 { type string; }
     leaf param2 { type string; }
     leaf param3 { type uint32; }
   }
   output {
     leaf result-message { type string; }
     leaf result-count { type uint32; }
   }
 }

The differences in each of the 4 source files are explained:

  • System H File

  • System C File

  • User H File

  • User C File

System H File: y_rpc1.h

Note

The new System H File can simply replace the old version. This is not a user file so there should not be any contents that need to be preserved.

The relevant edits are shown below for completeness, but this file should not need to be preserved.

  • Module revision constant:

    • This constant should be updated to the new revision-date

82c82
< #define y_r1_R_r1 (const xmlChar *)"2022-01-01"
---
> #define y_r1_R_r1 (const xmlChar *)"2022-02-01"
  • New leaf Constants:

    • These edits are optional, since the leaf name can be entered as a literal as needed.

#define y_r1_N_param3 (const xmlChar *)"param3"
#define y_r1_N_result_count (const xmlChar *)"result-count"

System C File: y_rpc1.c

Note

The new System C File can simply replace the old version. This is not a user file so there should not be any contents that need to be preserved.

The relevant edits are shown below for completeness, but this file should not need to be preserved.

  • New short names in the module comment

    • This edit is optional since it is part of a comment

/*
 ...

  param3 = /test1/input/param3
  result_count = /test1/output/result-count

 ...
*/

User H File: u_rpc1.h

Note

The new User H File can usually replace the old version. This file does not need to be merged unless user edits have been made to this file.

The relevant edits are shown below for completeness, but this file does not usually need to be preserved.

  • Struct typedef updates

    • The "typedef struct" statements for the RPC input and output should be updated to include the new parameter. These structs are not generally used by the server so this step is not usually required.

 /* container /test1/input */
 typedef struct y_r1_T_input_ {
     xmlChar *v_param1;
     xmlChar *v_param2;
     uint32 v_param3;
 } y_r1_T_input;

 /* container /test1/output */
 typedef struct y_r1_T_output_ {
     xmlChar *v_result_message;
     uint32 v_result_count;
 } y_r1_T_output;

User C File: u_rpc1.c

Note

The new User C File must be carefully merged to preserve existing code.

There will almost always be new code that must be merged into the existing User C file.

RPC Validate Function

  • New code for the param3 input parameter is added. This is a mandatory edit.

  • There is no new code generated for the message-cnt output leaf. The developer is expected to return the RPC output data, as described in the RPC Data Output Handling section.

 status_t u_r1_test1_validate (
     ses_cb_t *scb,
     rpc_msg_t *msg,
     xml_node_t *methnode)
 {
     status_t res = NO_ERR;
     val_value_t *errorval = NULL;

     if (LOGDEBUG) {
         log_debug("\nStart SIL validate rpc <test1> from module rpc1");
     }

     val_value_t *inputval = agt_get_rpc_input(msg);
     if (inputval == NULL) return ERR_NCX_OPERATION_FAILED;

     val_value_t *v_param1_val = NULL;
     const xmlChar *v_param1 = NULL;

     val_value_t *v_param2_val = NULL;
     const xmlChar *v_param2 = NULL;

     val_value_t *v_param3_val = NULL;
     uint32 v_param3 = 0;

     /* RPC Input: param1 */
     v_param1_val = val_find_child(
         inputval,
         y_r1_M_r1,
         y_r1_N_param1);
     if (v_param1_val) {
         v_param1 = VAL_STRING(v_param1_val);
         (void)v_param1;  /* remove this line if variable used */
     }

     /* RPC Input: param2 */
     v_param2_val = val_find_child(
         inputval,
         y_r1_M_r1,
         y_r1_N_param2);
     if (v_param2_val) {
         v_param2 = VAL_STRING(v_param2_val);
         (void)v_param2;  /* remove this line if variable used */
     }

     /* RPC Input: param3 */
     v_param3_val = val_find_child(
         inputval,
         y_r1_M_r1,
         y_r1_N_param3);
     if (v_param3_val) {
         v_param3 = VAL_UINT(v_param3_val);
         (void)v_param3;  /* remove this line if variable used */
     }

     if (res != NO_ERR) {
         agt_record_error(
             scb,
             &msg->mhdr,
             NCX_LAYER_OPERATION,
             res,
             methnode,
             (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
             errorval,
             (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
             errorval);
     }
     return res;

 } /* u_r1_test1_validate */

RPC Invoke Function

  • New code for the param3 input parameter is added. This is a mandatory edit. This auto-generated code is usually the same for the 'validate' and 'invoke' callbacks.

    This is not shown in the example code below because it is the same as the 'validate' callback.

  • There is no new code generated for the message-cnt output leaf, except a comment is updated (shown below).

  • The developer is expected to return the RPC output data, as described in the RPC Data Output Handling section.

 /* invoke your device instrumentation code here */

 /* Following output nodes expected:
  * leaf result-message
  * leaf result-count
  */

Adding a New RPC Operation

In this example, a new RPC operation named test2 is added to the YANG module.

 rpc test1 {
   input {
     leaf param1 { type string; }
     leaf param2 { type string; }
     leaf param3 { type uint32; }
   }
   output {
     leaf result-message { type string; }
     leaf result-count { type uint32; }
   }
 }

 rpc test2 {
    input {
      leaf test-param { type int32; }
    }
 }

System H File: y_rpc1.h (2)

This file can simply be replaced, as shown in the previous section. The same changes are made (i.e., revision-date and parameter name constants).

System C File: y_rpc1.c (2)

This file can simply be replaced, as shown in the previous section. The following edits are shown to highlight the code changes for adding a new RPC operation.

Setup the RPC Operation Object

  • The test2_obj must be set so the new RPC operation can be processed at run-time.

  • The test2_obj is declared as static data.

 /* module static variables */
 static ncx_module_t *rpc1_mod;
 static obj_template_t *test1_obj;
 static obj_template_t *test2_obj;

The test2_obj is initialized to NULL in the 'init static vars' function.

 static void y_r1_init_static_vars (void)
 {
     rpc1_mod = NULL;
     test1_obj = NULL;
     test2_obj = NULL;

 } /* y_r1_init_static_vars */

The 'init1' callback function is updated to set the test2_obj.

 test1_obj = ncx_find_object(
     rpc1_mod,
     y_r1_N_test1);
 if (test1_obj == NULL) {
     return ERR_NCX_DEF_NOT_FOUND;
 }

 test2_obj = ncx_find_object(
     rpc1_mod,
     y_r1_N_test2);
 if (test2_obj == NULL) {
     return ERR_NCX_DEF_NOT_FOUND;
 }

Register the Validate and Invoke Callbacks

The RPC Validate and RPC Invoke callbacks must be registered in the 'init1' function. The actual callback functions are defined in the User C file, but registered in this file.

It is not an error if one or both of these callbacks does not exist. It is not uncommon for the Validate callback to be missing, and for all the parameter validation to be done in the Invoke phase. This does not affect error processing for the RPC operation.

The 'init1' callback function y_rpc1_init is updated as shown below.

 res = agt_rpc_register_method(
     y_r1_M_r1,
     y_r1_N_test1,
     AGT_RPC_PH_VALIDATE,
     u_r1_test1_validate);
 if (res != NO_ERR) {
     return res;
 }

 res = agt_rpc_register_method(
     y_r1_M_r1,
     y_r1_N_test1,
     AGT_RPC_PH_INVOKE,
     u_r1_test1_invoke);
 if (res != NO_ERR) {
     return res;
 }

 res = agt_rpc_register_method(
     y_r1_M_r1,
     y_r1_N_test2,
     AGT_RPC_PH_VALIDATE,
     u_r1_test2_validate);
 if (res != NO_ERR) {
     return res;
 }

 res = agt_rpc_register_method(
     y_r1_M_r1,
     y_r1_N_test2,
     AGT_RPC_PH_INVOKE,
     u_r1_test2_invoke);
 if (res != NO_ERR) {
     return res;
 }

Unregister the Validate and Invoke Callbacks

The RPC operation must be unregistered in the 'cleanup' function. This step must be done or a small amount of memory can be leaked in the server.

The 'cleanup' callback y_rpc1_cleanup is shown below:

 void y_rpc1_cleanup (void)
 {

     if (LOGDEBUG) {
         log_debug("\nEnter y_rpc1_cleanup");
     }


     agt_rpc_unregister_method(
         y_r1_M_r1,
         y_r1_N_test1);

     agt_rpc_unregister_method(
         y_r1_M_r1,
         y_r1_N_test2);
     u_rpc1_cleanup();

 } /* y_rpc1_cleanup */

User H File: u_rpc1.h (2)

The relevant edits are shown below for completeness, but this file does not usually need to be preserved, unless it has been edited.

Typedefs for the RPC Input and Output Parameters

The struct typedefs for the /test2, /test2/input, and /test2/output nodes are created. These edits are optional, since these typedefs are not used by the server.

 /* rpc /test1 */
 typedef struct y_r1_T_test1_ {
     y_r1_T_input v_input;
     y_r1_T_output v_output;
 } y_r1_T_test1;

 /* container /test2/input */
 typedef struct y_r1_T_input_1_ {
     int32 v_test_param;
 } y_r1_T_input_1;

 /* container /test2/output */
 typedef struct y_r1_T_output_1_ {
 } y_r1_T_output_1;

 /* rpc /test2 */
 typedef struct y_r1_T_test2_ {
     y_r1_T_input_1 v_input;
     y_r1_T_output_1 v_output;
 } y_r1_T_test2;

Function Prototypes for the RPC Validate and Invoke Callbacks

The following 2 function prototypes for u_r1_test2_validate and u_r1_test2_invoke are added to the file, after the u_r1_test1_invoke function prototype.

/**
 * @brief Validation phase callback for "<test2>" operation. (agt_rpc_method_t)
 *
 * All YANG constraints have passed at this point.
 * Add description-stmt checks in this function.
 *
 * @param scb session invoking the RPC operation.
 * @param msg message in progress for this <rpc> request.
 * The msg->rpc_input value node contains the input (if any).
 * It is a container matching the rpc/input node for the YANG rpc.
 * @param methnode XML node for the operation, which can be used
 * in error reporting (or ignored).
 * @return return status for the phase.
 *  - An error in validate phase will cancel invoke phase
 *  - An rpc-error will be added if an error is returned and
 * the msg error Q is empty
 */
extern status_t u_r1_test2_validate (
    ses_cb_t *scb,
    rpc_msg_t *msg,
    xml_node_t *methnode);


/**
 * @brief Invocation phase callback for "<test2>" operation. (agt_rpc_method_t)
 *
 * Validation callback has passed at this point.
 * Call device instrumentation code in this function.
 *
 * @param scb session invoking the RPC operation.
 * @param msg message in progress for this <rpc> request.
 * The msg->rpc_input value node contains the input (if any).
 * It is a container matching the rpc/input node for the YANG rpc.
 * @param methnode XML node for the operation, which can be used
 * in error reporting (or ignored).
 * @return return status for the phase.
 *  - An error in validate phase will cancel invoke phase
 *  - An rpc-error will be added if an error is returned and
 * the msg error Q is empty
 */
extern status_t u_r1_test2_invoke (
    ses_cb_t *scb,
    rpc_msg_t *msg,
    xml_node_t *methnode);

User C File: u_rpc1.c (2)

There will almost always be new code that must be merged into the existing User C file.

In this example, the module header comment will be changed. That edit is not shown here.

RPC Validate and Invoke Functions

  • The following 2 function definitions for u_r1_test2_validate and u_r1_test2_invoke are added to the file, after the u_r1_test1_invoke function definition.

  • These functions are only called from the System C file (e.g., y_rpc1.c) and not from within the User C file.

/**
 * @brief Validation phase callback for "<test2>" operation. (agt_rpc_method_t)
 *
 * All YANG constraints have passed at this point.
 * Add description-stmt checks in this function.
 *
 * @param scb session invoking the RPC operation.
 * @param msg message in progress for this <rpc> request.
 * The msg->rpc_input value node contains the input (if any).
 * It is a container matching the rpc/input node for the YANG rpc.
 * @param methnode XML node for the operation, which can be used
 * in error reporting (or ignored).
 * @return return status for the phase.
 *  - An error in validate phase will cancel invoke phase
 *  - An rpc-error will be added if an error is returned and
 * the msg error Q is empty
 */
status_t u_r1_test2_validate (
    ses_cb_t *scb,
    rpc_msg_t *msg,
    xml_node_t *methnode)
{
    status_t res = NO_ERR;
    val_value_t *errorval = NULL;

    if (LOGDEBUG) {
        log_debug("\nStart SIL validate rpc <test2> from module rpc1");
    }

    val_value_t *inputval = agt_get_rpc_input(msg);
    if (inputval == NULL) return ERR_NCX_OPERATION_FAILED;

    val_value_t *v_test_param_val = NULL;
    int32 v_test_param = 0;

    /* RPC Input: test-param */
    v_test_param_val = val_find_child(
        inputval,
        y_r1_M_r1,
        y_r1_N_test_param);
    if (v_test_param_val) {
        v_test_param = VAL_INT(v_test_param_val);
        (void)v_test_param;  /* remove this line if variable used */
    }

    if (res != NO_ERR) {
        agt_record_error(
            scb,
            &msg->mhdr,
            NCX_LAYER_OPERATION,
            res,
            methnode,
            (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
            errorval,
            (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
            errorval);
    }
    return res;

} /* u_r1_test2_validate */


/**
 * @brief Invocation phase callback for "<test2>" operation. (agt_rpc_method_t)
 *
 * Validation callback has passed at this point.
 * Call device instrumentation code in this function.
 *
 * @param scb session invoking the RPC operation.
 * @param msg message in progress for this <rpc> request.
 * The msg->rpc_input value node contains the input (if any).
 * It is a container matching the rpc/input node for the YANG rpc.
 * @param methnode XML node for the operation, which can be used
 * in error reporting (or ignored).
 * @return return status for the phase.
 *  - An error in validate phase will cancel invoke phase
 *  - An rpc-error will be added if an error is returned and
 * the msg error Q is empty
 */
status_t u_r1_test2_invoke (
    ses_cb_t *scb,
    rpc_msg_t *msg,
    xml_node_t *methnode)
{
    status_t res = NO_ERR;

    if (LOGDEBUG) {
        log_debug("\nStart SIL invoke rpc <test2> from module rpc1");
    }

    val_value_t *inputval = agt_get_rpc_input(msg);
    if (inputval == NULL) return ERR_NCX_OPERATION_FAILED;

    val_value_t *v_test_param_val = NULL;
    int32 v_test_param = 0;

    /* RPC Input: test-param */
    v_test_param_val = val_find_child(
        inputval,
        y_r1_M_r1,
        y_r1_N_test_param);
    if (v_test_param_val) {
        v_test_param = VAL_INT(v_test_param_val);
        (void)v_test_param;  /* remove this line if variable used */
    }

    /* remove the next line if scb is used */
    (void)scb;

    /* remove the next line if methnode is used */
    (void)methnode;

    /* invoke your device instrumentation code here */

    /* No output nodes expected */

    return res;

} /* u_r1_test2_invoke */

Updating Notifications

This section demonstrates different code changes related to notification send functions. The 'send' function is not a callback that needs to be registered. It is completely optional to use, and generated only as a convenience. Refer to the Notifications section for details on the 'send' function.

Adding Parameters to a Notification

One common update use-case is to add new parameters to an existing notification statement.

In this example, a new leaf parameter named start-time is added to the notif1 event in the YANG module (notif1.yang).

   notification notif1 {
     leaf data1 { type string; }
     leaf data2 { type string; }
     leaf start-time { type string; }
   }

The differences in each of the 4 source files are explained:

System H File: y_notif1.h

This file can simply be replaced, as shown in previous sections. The changes to this file are similar to System H file for RPC operations, as shown in the System H File: y_rpc1.h section.

In this example, a constant for the leaf name is added for the start-time leaf. These edits are not shown here.

System C File: y_notif1.c

This file can simply be replaced, as shown in previous sections. There are no changes to this file for a notification 'send' function, other than the update to the module header comment.

each new schema node, if the --short-names=true option is used in the code generation (default).

The name mapping in the module comment will identify the name string given to each new schema node, if the --short-names=true option is used in the code generation (default).

User H File: u_notif1.h

This file can usually be replaced, as shown in previous sections. The relevant edits are shown below for completeness, but this file does not usually need to be preserved, unless it has been edited.

Add the New Parameter to the Notification Typedef

In this example, the v_start_time string is added to the struct typedef for the notification. This edit is optional since this struct is not used by the server.

 /* notification /notif1 */
 typedef struct y_n1_T_notif1_ {
     xmlChar *v_data1;
     xmlChar *v_data2;
     xmlChar *v_start_time;
 } y_n1_T_notif1;

Add the New Parameter to the Notification Function Prototype

In this example, the v_start_time parameter is added to the function prototype for the 'send' function named u_n1_notif1_send.

 /**
  * @brief Send a "<notif1>" notification.
  *
  * Called by your code when notification event occurs.
  *
  * This API is optional. It can be replaced with
  * any function of your choice that provides the same functionality.
  *
  * Create an internal notification message (agt_not_msg_t) and queue
  * it for delivery to client sessions.
  *
  * The parameters depend in the data definitions within the
  * notification-stmt for this event type.
  */
 extern void u_n1_notif1_send (
     const xmlChar *v_data1,
     const xmlChar *v_data2,
     const xmlChar *v_start_time);

User C File: u_notif1.c

There will almost always be new code that must be merged into the existing User C file. In this example, the module header comment will be changed. That edit is not shown here.

Add the Send Function Prototype

The v_start_time parameter is added to the function definition for the 'send' function named u_n1_notif1_send.

 void u_n1_notif1_send (
     const xmlChar *v_data1,
     const xmlChar *v_data2,
     const xmlChar *v_start_time)

Add Code to for the New Parameter to the Notification

The v_start_time parameter is added to the notification message in the 'send' function named u_n1_notif1_send.

/* add start_time to payload */
parmval = agt_make_leaf2(
    notif1_obj,
    y_n1_M_n1,
    y_n1_N_start_time,
    v_start_time,
    &res);
if (parmval == NULL) {
    log_error("\nError: Cannot add leaf 'start-time' to payload (%s)",
        get_error_string(res));
} else {
    agt_not_add_to_payload(notif, parmval);
}

Adding A New Notification

To add a new notification, usually only the new code-stub in the User C file needs to be filled in. All other files can usually be replaced with the new versions.

In this example, a new notification named notif2 is added to the YANG module (notif1.yang)

   notification notif1 {
     leaf data1 { type string; }
     leaf data2 { type string; }
     leaf start-time { type string; }
   }

   notification notif2 {
     container data {
       leaf report1 { type string; }
       leaf report2 { type string; }
     }
     leaf end-time { type string; }
   }

The differences in each of the 4 source files are explained:

System H File: y_notif1.h (2)

This file can simply be replaced, as shown in previous sections. The changes to this file are similar to System H file for RPC operations, as shown in the System H File: y_rpc1.h section.

In this example, new constants for the new node names in notif2 are added to the file y_notif1.h. These edits are not shown here.

System C File: y_notif1.c (2)

This file can simply be replaced, as shown in previous sections. There are no changes to this file for a notification 'send' function, other than the update to the module header comment.

The name mapping in the module comment will identify the name string given to each new schema node, if the --short-names=true option is used in the code generation (default).

User H File: u_notif1.h (2)

This file can usually be replaced, as shown in previous sections. The relevant edits are shown below for completeness, but this file does not usually need to be preserved, unless it has been edited.

Add New Notification Struct Typedefs

In this example, there are 2 typedefs structs added to this file.

 /* notification /notif1 */
 typedef struct y_n1_T_notif1_ {
     xmlChar *v_data1;
     xmlChar *v_data2;
     xmlChar *v_start_time;
 } y_n1_T_notif1;

 /* container /notif2/data */
 typedef struct y_n1_T_data_ {
     xmlChar *v_report1;
     xmlChar *v_report2;
 } y_n1_T_data;

 /* notification /notif2 */
 typedef struct y_n1_T_notif2_ {
     y_n1_T_data v_data;
     xmlChar *v_end_time;
 } y_n1_T_notif2;

Add the Notification Send Function Prototype

In this example, the u_n1_notif2_send function prototype is added to the file.

 extern void u_n1_notif1_send (
     const xmlChar *v_data1,
     const xmlChar *v_data2,
     const xmlChar *v_start_time);


 /**
  * @brief Send a "<notif2>" notification.
  *
  * Called by your code when notification event occurs.
  *
  * This API is optional. It can be replaced with
  * any function of your choice that provides the same functionality.
  *
  * Create an internal notification message (agt_not_msg_t) and queue
  * it for delivery to client sessions.
  *
  * The parameters depend in the data definitions within the
  * notification-stmt for this event type.
  */
 extern void u_n1_notif2_send (
     y_n1_T_data *v_data,
     const xmlChar *v_end_time);

User C File: u_notif1.c (2)

There will almost always be new code that must be merged into the existing User C file. In this example, the module header comment will be changed. That edit is not shown here.

Add a New Object Variable

  • The notif2_obj must be added, similar to the object template.

 static obj_template_t *notif1_obj;
 static obj_template_t *notif2_obj;

Add the Send Function Definition

The u_n1_notif2_send function definition is added to the file.

/**
 * @brief Send a "<notif2>" notification.
 *
 * Called by your code when notification event occurs.
 *
 * This API is optional. It can be replaced with
 * any function of your choice that provides the same functionality.
 *
 * Create an internal notification message (agt_not_msg_t) and queue
 * it for delivery to client sessions.
 *
 * The parameters depend in the data definitions within the
 * notification-stmt for this event type.
 */
void u_n1_notif2_send (
    y_n1_T_data *v_data,
    const xmlChar *v_end_time)
{
    val_value_t *parmval = NULL;
    status_t res = NO_ERR;

    if (!agt_notifications_enabled()) {
        log_debug2("\nSkipping <notif2> notification; disabled");
        return;
    }

    if (LOGDEBUG) {
        log_debug("\nGenerating <notif2> notification");
    }

    agt_not_msg_t *notif = agt_not_new_notification(notif2_obj);
    if (notif == NULL) {
        log_error("\nError: malloc failed, cannot send "
        "<notif2> notification");
        return;
    }

    /* add container data to payload
     * replace following line with real code
     */
    (void)v_data;

    /* add end_time to payload */
    parmval = agt_make_leaf2(
        notif2_obj,
        y_n1_M_n1,
        y_n1_N_end_time,
        v_end_time,
        &res);
    if (parmval == NULL) {
        log_error("\nError: Cannot add leaf 'end-time' to payload (%s)",
            get_error_string(res));
    } else {
        agt_not_add_to_payload(notif, parmval);
    }

    agt_not_queue_notification(notif);

} /* u_n1_notif2_send */

Note

The SIL-SA variant of the 'send' function is different than the SIL version.

  • SIL: use agt_not_queue_notification

  • SIL-SA: use sil_sa_queue_notification

Update the init Callback to set the Notification Object

  • the notif2_obj variable must be set in the 'init' function callback. In this example, the u_notif1_init function is updated.

 status_t u_notif1_init (
     const xmlChar *modname,
     const xmlChar *revision)
 {
     status_t res = NO_ERR;

     if (LOGDEBUG) {
         log_debug("\nEnter u_notif1_init");
     }

     ncx_module_t *notif1_mod = NULL;

     notif1_mod = ncx_find_module(modname, revision);
     if (notif1_mod == NULL) {
         return ERR_NCX_OPERATION_FAILED;
     }

     notif1_obj = ncx_find_object(
         notif1_mod,
         y_n1_N_notif1);
     if (notif1_obj == NULL) {
         return ERR_NCX_DEF_NOT_FOUND;
     }

     notif2_obj = ncx_find_object(
         notif1_mod,
         y_n1_N_notif2);
     if (notif2_obj == NULL) {
         return ERR_NCX_DEF_NOT_FOUND;
     }

     /* put your module initialization code here */

     return res;

 } /* u_notif1_init */

Updating Configuration Data Nodes

Data nodes should use the EDIT2 Callback or EDIT3 Callback callback style. This section demonstrates different code changes related to configuration data callback functions.

Note

EDIT3 does not utilize the same callback template as EDIT1 and EDIT2, nor does it use the same registration functions. However, the update process follows the same steps. Refer to EDIT3 Callback for more details on how to handle EDIT3 callbacks registration.

Adding A Nested List to an Existing List

In this example, a new list named part is added to the widget list in the YANG module (configdata1.yang).

   container widgets {
     list widget {
       key name;
       leaf name { type string; }
       leaf parm1 { type int32; }
       leaf parm2 { type string; }
       list part {
         key part-id;
         leaf part-id { type string; }
         leaf build-date { type string; }
         leaf bin-num { type uint32; }
       }
     }
   }

The differences in each of the 4 source files are explained.

System H File: y_configdata1.h

This file can simply be replaced, as shown in the previous section. The same changes are made (i.e., revision-date and parameter name constants).

#define y_cd1_N_bin_num (const xmlChar *)"bin-num"
#define y_cd1_N_build_date (const xmlChar *)"build-date"

#define y_cd1_N_part (const xmlChar *)"part"
#define y_cd1_N_part_id (const xmlChar *)"part-id"

System C File: y_configdata1.c

This file can simply be replaced, as shown in the previous section. The following edits are shown to highlight the code changes for adding a new nested configuration data list.

Add an EDIT2 Callback for the List

The part_edit function is added to the C file. This is a static function so there is no prototype for it in the H file.

/**
 * @brief Edit database object callback (agt_cb_fn_t)\n
 * Path: list /widgets/widget/part
 *
 * @param scb session control block making the request
 * @param msg message in progress for this edit request
 * @param cbtyp callback type for this callback
 * @param editop the parent edit-config operation type,
 * which is also used for all other callbacks
 * that operate on objects.
 * @param newval container object holding the proposed changes
 * to apply to the current config, depending on
 * the editop value. Will not be NULL.
 * @param curval current container values from the "<running>"
 * or "<candidate>" configuration, if any. Could be NULL
 * for create and other operations.
 * @return return status for the phase.
 */
static status_t part_edit (
    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);
    val_value_t *lastkey = NULL;

    /* ancestor key configdata1:name */
    const xmlChar *k_cd1_name =
        VAL_STRING(agt_get_key_value(errorval, &lastkey));

    /* local key configdata1:part-id */
    const xmlChar *k_cd1_part_id =
        VAL_STRING(agt_get_key_value(errorval, &lastkey));

    if (LOGDEBUG) {
        log_debug("\nEnter part_edit callback for %s phase",
            agt_cbtype_name(cbtyp));
    }

    res = u_cd1_part_edit(scb, msg, cbtyp, editop, newval, curval,
        k_cd1_name,
        k_cd1_part_id);

    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;

} /* part_edit */

Register the New Callback in the Init Function

The EDIT2 callback named part_edit must be registered. This is done in the y_configdata1_init function, which is one of the three mandatory APIs for SIL or SIL-SA code.

 res = agt_cb_register_edit2_callback(
     y_cd1_M_cd1,
     (const xmlChar *)"/cd1:widgets/cd1:widget",
     y_cd1_R_cd1,
     widget_edit);
 if (res != NO_ERR) {
     return res;
 }

 res = agt_cb_register_edit2_callback(
     y_cd1_M_cd1,
     (const xmlChar *)"/cd1:widgets/cd1:widget/cd1:part",
     y_cd1_R_cd1,
     part_edit);
 if (res != NO_ERR) {
     return res;
 }

Unregister the New Callback in the Cleanup Function

The EDIT2 callback named part_edit must be unregistered. This is done in the y_configdata1_cleanup function, which is one of the three mandatory APIs for SIL or SIL-SA code.

 agt_cb_unregister_callbacks(
     y_cd1_M_cd1,
     (const xmlChar *)"/cd1:widgets/cd1:widget");

 agt_cb_unregister_callbacks(
     y_cd1_M_cd1,
     (const xmlChar *)"/cd1:widgets/cd1:widget/cd1:part");
 u_configdata1_cleanup();

User H File: u_configdata1.h

This file can usually be replaced, as shown in previous sections. The relevant edits are shown below for completeness, but this file does not usually need to be preserved, unless it has been edited.

Add A New Data Node Struct Typedef

In this example, there is 1 typedef struct added to this file.

/* list /widgets/widget/part */
typedef struct y_cd1_T_part_ {
    dlq_hdr_t qhdr;
    xmlChar *v_part_id;
    xmlChar *v_build_date;
    uint32 v_bin_num;
} y_cd1_T_part;

Add A Function Prototype for the New EDIT2 Callback

In this example, there is 1 Callback function prototype added to the file.

/**
 * @brief Edit database object callback (agt_cb_fn_t)\n
 * Path: list /widgets/widget/part
 *
 * @param scb session control block making the request
 * @param msg message in progress for this edit request
 * @param cbtyp callback type for this callback
 * @param editop the parent edit-config operation type,
 * which is also used for all other callbacks
 * that operate on objects.
 * @param newval container object holding the proposed changes
 * to apply to the current config, depending on
 * the editop value. Will not be NULL.
 * @param curval current container values from the "<running>"
 * or "<candidate>" configuration, if any. Could be NULL
 * for create and other operations.
 * @param k_cd1_name Ancestor key leaf 'name' in list 'widget'\n
 * Path: /cd1:widgets/cd1:widget/cd1:name
 * @param k_cd1_part_id Local key leaf 'part-id' in list 'part'\n
 * Path: /cd1:widgets/cd1:widget/cd1:part/cd1:part-id
 * @return return status for the phase.
 */
extern status_t u_cd1_part_edit (
    ses_cb_t *scb,
    rpc_msg_t *msg,
    agt_cbtyp_t cbtyp,
    op_editop_t editop,
    val_value_t *newval,
    val_value_t *curval,
    const xmlChar *k_cd1_name,
    const xmlChar *k_cd1_part_id);

User C File: u_configdata1.c

There will almost always be new code that must be merged into the existing User C file. In this example, the module header comment will be changed. That edit is not shown here.

Add A Function Definition for the New EDIT2 Callback

In this example, there is 1 Callback function definition added to the file.

/**
 * @brief Edit database object callback (agt_cb_fn_t)\n
 * Path: list /widgets/widget/part
 *
 * @param scb session control block making the request
 * @param msg message in progress for this edit request
 * @param cbtyp callback type for this callback
 * @param editop the parent edit-config operation type,
 * which is also used for all other callbacks
 * that operate on objects.
 * @param newval container object holding the proposed changes
 * to apply to the current config, depending on
 * the editop value. Will not be NULL.
 * @param curval current container values from the "<running>"
 * or "<candidate>" configuration, if any. Could be NULL
 * for create and other operations.
 * @param k_cd1_name Ancestor key leaf 'name' in list 'widget'\n
 * Path: /cd1:widgets/cd1:widget/cd1:name
 * @param k_cd1_part_id Local key leaf 'part-id' in list 'part'\n
 * Path: /cd1:widgets/cd1:widget/cd1:part/cd1:part-id
 * @return return status for the phase.
 */
status_t u_cd1_part_edit (
    ses_cb_t *scb,
    rpc_msg_t *msg,
    agt_cbtyp_t cbtyp,
    op_editop_t editop,
    val_value_t *newval,
    val_value_t *curval,
    const xmlChar *k_cd1_name,
    const xmlChar *k_cd1_part_id)
{
    (void)scb;  /* remove if scb used */
    (void)msg;  /* remove if msg used */
    (void)newval;  /* remove if newval used */
    (void)curval;  /* remove if curval used */

    status_t res = NO_ERR;

    if (LOGDEBUG) {
        log_debug("\nEnter u_cd1_part_edit callback for %s phase",
            agt_cbtype_name(cbtyp));
    }

    if (editop == OP_EDITOP_MERGE) {
        /* 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) {
            op_editop_t child_editop = OP_EDITOP_NONE;
            val_value_t *child_newval = NULL;
            val_value_t *child_curval = NULL;

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

            if (LOGDEBUG2) {
                log_debug2("\nchild edit %s", op_editop_name(child_editop));
                if (child_newval) {
                    log_debug2_append("\nnew");
                    val_dump_value(child_newval, 0, DBG2);
                }
                if (child_curval) {
                    log_debug2_append("\ncur");
                    val_dump_value(child_curval, 0, DBG2);
                }
            }

            obj_template_t *chobj = (child_newval) ?
                VAL_OBJ(child_newval) : VAL_OBJ(child_curval);
            const xmlChar *name = obj_get_name(chobj);

            /**** process child edits here ****/

            if (!xml_strcmp(name, y_cd1_N_build_date)) {
                /* leaf build-date (string) */

            } else if (!xml_strcmp(name, y_cd1_N_bin_num)) {
                /* leaf bin-num (uint32) */

            }

            child_edit = agt_cfg_next_child_edit(child_edit);
        }
    }

    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:
            break;
        case OP_EDITOP_REPLACE:
            break;
        case OP_EDITOP_CREATE:
            break;
        case OP_EDITOP_DELETE:
            break;
        default:
            /* USE SET_ERROR FOR PROGRAMMING BUGS ONLY */
            res = SET_ERROR(ERR_INTERNAL_VAL);
        }
        break;
    case AGT_CB_ROLLBACK:
        /* undo device instrumentation here */
        break;
    default:
        /* USE SET_ERROR FOR PROGRAMMING BUGS ONLY */
        res = SET_ERROR(ERR_INTERNAL_VAL);
    }
    return res;

} /* u_cd1_part_edit */

Adding A New Top Level Container

In this example, a new top-level container named global-data is added to the YANG module (configdata1.yang).

   container widgets {
     list widget {
       key name;
       leaf name { type string; }
       leaf parm1 { type int32; }
       leaf parm2 { type string; }
       list part {
         key part-id;
         leaf part-id { type string; }
         leaf build-date { type string; }
         leaf bin-num { type uint32; }
       }
     }
   }

   container global-data {
     leaf top-widget {
       type leafref {
         path "/widgets/widget/name";
       }
     }
     leaf report-warnings { type boolean; }
     leaf report-errors { type boolean; }
   }

The differences in each of the 4 source files are explained.

System H File: y_configdata1.h (2)

This file can simply be replaced, as shown in the previous section. The same changes are made (i.e., revision-date and parameter name constants).

#define y_cd1_N_global_data (const xmlChar *)"global-data"

#define y_cd1_N_report_errors (const xmlChar *)"report-errors"
#define y_cd1_N_report_warnings (const xmlChar *)"report-warnings"
#define y_cd1_N_top_widget (const xmlChar *)"top-widget"

System C File: y_configdata1.c (2)

This file can simply be replaced, as shown in the previous section. The following edits are shown to highlight the changes required for adding a new top-level configuration data node.

Add Static Data Definitions for the New Object

The global_data_obj variable is used to save the object template for the new top-level node. This variable is mandatory.

The global_data_val variable is used to save a cache pointer to this data node in the <running> datastore. This variable is optional to use.

 /* module static variables */
 static ncx_module_t *configdata1_mod;
 static obj_template_t *widgets_obj;
 static obj_template_t *global_data_obj;
 static val_value_t *widgets_val;
 static val_value_t *global_data_val;

Initialize the New Static Data

The Init Static Vars function (y_cd1_init_static_vars) must be updated to initialize the new static variables.

 static void y_cd1_init_static_vars (void)
 {
     configdata1_mod = NULL;
     widgets_obj = NULL;
     global_data_obj = NULL;
     widgets_val = NULL;
     global_data_val = NULL;

 } /* y_cd1_init_static_vars */

Add an EDIT2 Callback Function for the New Container

  • The global_data_edit EDIT2 callback function is added to the file.

/**
 * @brief Edit database object callback (agt_cb_fn_t)\n
 * Path: container /global-data
 *
 * @param scb session control block making the request
 * @param msg message in progress for this edit request
 * @param cbtyp callback type for this callback
 * @param editop the parent edit-config operation type,
 * which is also used for all other callbacks
 * that operate on objects.
 * @param newval container object holding the proposed changes
 * to apply to the current config, depending on
 * the editop value. Will not be NULL.
 * @param curval current container values from the "<running>"
 * or "<candidate>" configuration, if any. Could be NULL
 * for create and other operations.
 * @return return status for the phase.
 */
static status_t global_data_edit (
    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);

    if (LOGDEBUG) {
        log_debug("\nEnter global_data_edit callback for %s phase",
            agt_cbtype_name(cbtyp));
    }

    res = u_cd1_global_data_edit(scb, msg, cbtyp, editop, newval, curval);

    if (res == NO_ERR && cbtyp == AGT_CB_COMMIT) {
        res = agt_check_cache(&global_data_val, newval, curval, editop);
    }


    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;

} /* global_data_edit */

Set the Static Data During the Init Function

  • The global_data_obj pointer is set during the y_configdata1_init function.

 widgets_obj = ncx_find_object(
     configdata1_mod,
     y_cd1_N_widgets);
 if (widgets_obj == NULL) {
     return ERR_NCX_DEF_NOT_FOUND;
 }

 global_data_obj = ncx_find_object(
     configdata1_mod,
     y_cd1_N_global_data);
 if (global_data_obj == NULL) {
     return ERR_NCX_DEF_NOT_FOUND;
 }

Register the EDIT2 Callback Function During the Init Function

  • The global_data_edit callback function is registered during the y_configdata1_init function.

 res = agt_cb_register_edit2_callback(
     y_cd1_M_cd1,
     (const xmlChar *)"/cd1:widgets/cd1:widget/cd1:part",
     y_cd1_R_cd1,
     part_edit);
 if (res != NO_ERR) {
     return res;
 }

 res = agt_cb_register_edit2_callback(
     y_cd1_M_cd1,
     (const xmlChar *)"/cd1:global-data",
     y_cd1_R_cd1,
     global_data_edit);
 if (res != NO_ERR) {
     return res;
 }

Set the Cache Pointer For the Data Node During the Init2 Function

  • The global_data_val cache pointer is set during the y_configdata1_init2 function. The use of this pointer is optional so this step is optional.

 global_data_val = agt_init_cache(
     y_cd1_M_cd1,
     y_cd1_N_global_data,
     &res);
 if (res != NO_ERR) {
     return res;
 }

 widgets_val = agt_init_cache(
     y_cd1_M_cd1,
     y_cd1_N_widgets,
     &res);
 if (res != NO_ERR) {
     return res;
 }

Unregister the EDIT2 Callback Function During the Cleanup Function

  • The global_data_edit callback function is unregistered during the y_configdata1_cleanup function.

 agt_cb_unregister_callbacks(
     y_cd1_M_cd1,
     (const xmlChar *)"/cd1:widgets/cd1:widget/cd1:part");

 agt_cb_unregister_callbacks(
     y_cd1_M_cd1,
     (const xmlChar *)"/cd1:global-data");
 u_configdata1_cleanup();

User H File: u_configdata1.h (2)

This file can usually be replaced, as shown in previous sections. The relevant edits are shown below for completeness, but this file does not usually need to be preserved, unless it has been edited.

Add A New Data Node Struct Typedef

In this example, there is 1 typedef struct added to this file.

/* container /global-data */
typedef struct y_cd1_T_global_data_ {
    xmlChar *v_top_widget;
    boolean v_report_warnings;
    boolean v_report_errors;
} y_cd1_T_global_data;

Add a Function Prototype for the New EDIT2 Callback

The u_cd1_global_data_edit callback function is registered during the y_configdata1_init function. It is invoked by the global_data_edit function in the System C File.

/**
 * @brief Edit database object callback (agt_cb_fn_t)\n
 * Path: container /global-data
 *
 * @param scb session control block making the request
 * @param msg message in progress for this edit request
 * @param cbtyp callback type for this callback
 * @param editop the parent edit-config operation type,
 * which is also used for all other callbacks
 * that operate on objects.
 * @param newval container object holding the proposed changes
 * to apply to the current config, depending on
 * the editop value. Will not be NULL.
 * @param curval current container values from the "<running>"
 * or "<candidate>" configuration, if any. Could be NULL
 * for create and other operations.
 * @return return status for the phase.
 */
extern status_t u_cd1_global_data_edit (
    ses_cb_t *scb,
    rpc_msg_t *msg,
    agt_cbtyp_t cbtyp,
    op_editop_t editop,
    val_value_t *newval,
    val_value_t *curval);

User C File: u_configdata1.c (2)

There will almost always be new code that must be merged into the existing User C file. In this example, the module header comment will be changed. That edit is not shown here.

Add A Function Definition for the New EDIT2 Callback

In this example, there is 1 Callback function definition added to the file.

/**
 * @brief Edit database object callback (agt_cb_fn_t)\n
 * Path: container /global-data
 *
 * @param scb session control block making the request
 * @param msg message in progress for this edit request
 * @param cbtyp callback type for this callback
 * @param editop the parent edit-config operation type,
 * which is also used for all other callbacks
 * that operate on objects.
 * @param newval container object holding the proposed changes
 * to apply to the current config, depending on
 * the editop value. Will not be NULL.
 * @param curval current container values from the "<running>"
 * or "<candidate>" configuration, if any. Could be NULL
 * for create and other operations.
 * @return return status for the phase.
 */
status_t u_cd1_global_data_edit (
    ses_cb_t *scb,
    rpc_msg_t *msg,
    agt_cbtyp_t cbtyp,
    op_editop_t editop,
    val_value_t *newval,
    val_value_t *curval)
{
    (void)scb;  /* remove if scb used */
    (void)msg;  /* remove if msg used */
    (void)newval;  /* remove if newval used */
    (void)curval;  /* remove if curval used */

    status_t res = NO_ERR;

    if (LOGDEBUG) {
        log_debug("\nEnter u_cd1_global_data_edit callback for %s phase",
            agt_cbtype_name(cbtyp));
    }

    if (editop == OP_EDITOP_MERGE) {
        /* 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) {
            op_editop_t child_editop = OP_EDITOP_NONE;
            val_value_t *child_newval = NULL;
            val_value_t *child_curval = NULL;

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

            if (LOGDEBUG2) {
                log_debug2("\nchild edit %s", op_editop_name(child_editop));
                if (child_newval) {
                    log_debug2_append("\nnew");
                    val_dump_value(child_newval, 0, DBG2);
                }
                if (child_curval) {
                    log_debug2_append("\ncur");
                    val_dump_value(child_curval, 0, DBG2);
                }
            }

            obj_template_t *chobj = (child_newval) ?
                VAL_OBJ(child_newval) : VAL_OBJ(child_curval);
            const xmlChar *name = obj_get_name(chobj);

            /**** process child edits here ****/

            if (!xml_strcmp(name, y_cd1_N_top_widget)) {
                /* leaf top-widget (leafref) */

            } else if (!xml_strcmp(name, y_cd1_N_report_warnings)) {
                /* leaf report-warnings (boolean) */

            } else if (!xml_strcmp(name, y_cd1_N_report_errors)) {
                /* leaf report-errors (boolean) */

            }

            child_edit = agt_cfg_next_child_edit(child_edit);
        }
    }

    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:
            break;
        case OP_EDITOP_REPLACE:
            break;
        case OP_EDITOP_CREATE:
            break;
        case OP_EDITOP_DELETE:
            break;
        default:
            /* USE SET_ERROR FOR PROGRAMMING BUGS ONLY */
            res = SET_ERROR(ERR_INTERNAL_VAL);
        }
        break;
    case AGT_CB_ROLLBACK:
        /* undo device instrumentation here */
        break;
    default:
        /* USE SET_ERROR FOR PROGRAMMING BUGS ONLY */
        res = SET_ERROR(ERR_INTERNAL_VAL);
    }
    return res;

} /* u_cd1_global_data_edit */

Adding A New Leaf to a Nested List

In this example, a new optional leaf named description is added to the widget and part lists in the YANG module (configdata1.yang).

   container widgets {
     list widget {
       key name;
       leaf name { type string; }
       leaf parm1 { type int32; }
       leaf parm2 { type string; }
       leaf description { type string; }
       list part {
         key part-id;
         leaf part-id { type string; }
         leaf build-date { type string; }
         leaf bin-num { type uint32; }
         leaf description { type string; }
       }
     }
   }

The differences in each of the 4 source files are explained.

System H File: y_configdata1.h (3)

This file can simply be replaced, as shown in the previous section. The same changes are made (i.e., revision-date and parameter name constants).

#define y_cd1_N_description (const xmlChar *)"description"

System C File: y_configdata1.c (3)

This file can simply be replaced, as shown in the previous section. Since the EDIT2 callbacks are being used, there are no code changes to this file except that the module header comment is updated.

Note

It is important to check the short name assignments, especially when the same data node name is used more than once in the module.

In this example the description leaf is added twice, so there will be a numbered identifier label created for one of the duplicates. It is important to note which path is assigned to each identifier.

/*
  ...

     description = /widgets/widget/description
     description_1 = /widgets/widget/part/description

  ...

*/

User H File: u_configdata1.h (3)

This file can usually be replaced, as shown in previous sections. The relevant edits are shown below for completeness, but this file does not usually need to be preserved, unless it has been edited.

Add A New Struct Member to the Data Node Struct Typedefs

The optional struct typedefs are updated to add the new description leaf to each list.

 /* list /widgets/widget/part */
 typedef struct y_cd1_T_part_ {
     dlq_hdr_t qhdr;
     xmlChar *v_part_id;
     xmlChar *v_build_date;
     uint32 v_bin_num;
     xmlChar *v_description;
 } y_cd1_T_part;

 /* list /widgets/widget */
 typedef struct y_cd1_T_widget_ {
     dlq_hdr_t qhdr;
     xmlChar *v_name;
     int32 v_parm1;
     xmlChar *v_parm2;
     xmlChar *v_description;
     dlq_hdr_t v_part;
 } y_cd1_T_widget;

User C File: u_configdata1.c (3)

There will almost always be new code that must be merged into the existing User C file. In this example, the module header comment will be changed. That edit is not shown here.

Check for the New Child Nodes in the Existing EDIT2 Callbacks

  • The description leaf is checked as a child edit if the list entry is being modified.

  • There is no specific check added for the 'create' case, but the new leaf will be present within the provided newval parameter.

This example shows the updated section in the u_cd1_part_edit function. The same edit is also made in the u_cd1_widget_edit function.

         const xmlChar *name = obj_get_name(chobj);

         /**** process child edits here ****/

         if (!xml_strcmp(name, y_cd1_N_build_date)) {
             /* leaf build-date (string) */

         } else if (!xml_strcmp(name, y_cd1_N_bin_num)) {
             /* leaf bin-num (uint32) */

         } else if (!xml_strcmp(name, y_cd1_N_description)) {
             /* leaf description (string) */

         }

Updating Operational Data Nodes

This section demonstrates different code changes related to operational data. Only GET2 Callback Function examples are shown, since GET1 Callback Function callbacks are deprecated and should not be used.

Adding a New Container to an Existing List

This example shows the code changes that would occur if a new complex schema node is added to an existing parent schema node. In this case the data-errors container is added to the existing port list, in the module 'operdata1.yang'. A new GET2 callback for this container must be added to the SIL or SIL-SA code.

   container stats {
     config false;

     list port {
       key port-num;
       leaf port-num { type uint16; }
       leaf in-bytes { type yang:counter32; }
       leaf out-bytes { type yang:counter32; }
       leaf in-errors { type yang:counter32; }
       leaf out-errors { type yang:counter32; }

       container data-errors {
         leaf encode-fails { type yang:counter32; }
         leaf decode-fails { type yang:counter32; }
         leaf error-rate {
           type decimal64 { fraction-digits 4; }
         }
       }
     }
   }

The differences in each of the 4 source files are explained:

System H File: y_operdata1.h

This file can simply be replaced, as shown in the previous section. The same changes are made (i.e., revision-date and parameter name constants).

#define y_op1_N_data_errors (const xmlChar *)"data-errors"
#define y_op1_N_decode_fails (const xmlChar *)"decode-fails"
#define y_op1_N_encode_fails (const xmlChar *)"encode-fails"
#define y_op1_N_error_rate (const xmlChar *)"error-rate"

System C File: y_operdata1.c

This file can simply be replaced. The code changes to add a new data node are shown here.

Add GET2 Function For the New Container

The data_errors_get static function is added to this file.

/**
 * @brief Get database object callback for container data-errors (getcb_fn2_t)\n
 * Path: container /stats/port/data-errors\n
 *
 * Fill in 'get2cb' response fields.
 *
 * @param scb session control block making the request.
 * @param msg message in progress for this retrieval request.
 * @param get2cb GET2 control block for the callback.
 * @return return status of the callback.
 */
static status_t data_errors_get (
    ses_cb_t *scb,
    xml_msg_hdr_t *msg,
    getcb_get2_t *get2cb)
{

    if (LOGDEBUG) {
        log_debug("\nEnter data_errors_get");
    }

    (void)scb;
    (void)msg;

    val_value_t *keyval = NULL;

    /* ancestor key operdata1:port-num */
    uint16 k_op1_port_num = 0;
    keyval = GETCB_GET2_FIRST_KEY(get2cb);
    if (keyval) {
        k_op1_port_num = VAL_UINT16(keyval);
    }

    return u_op1_data_errors_get(
        get2cb,
        k_op1_port_num);

} /* data_errors_get */

Register the GET2 Callback in the Init Function

The data_errors_get GET2 callback is registered in the y_operdata1_init function.

 res = agt_cb_register_get_callback(
     y_op1_M_op1,
     (const xmlChar *)"/op1:stats/op1:port",
     y_op1_R_op1,
     port_get);
 if (res != NO_ERR) {
     return res;
 }

 res = agt_cb_register_get_callback(
     y_op1_M_op1,
     (const xmlChar *)"/op1:stats/op1:port/op1:data-errors",
     y_op1_R_op1,
     data_errors_get);
 if (res != NO_ERR) {
     return res;
 }

Unregister the GET2 Callback in the Cleanup Function

The data_errors_get GET2 callback is unregistered in the y_operdata1_cleanup function.

 void y_operdata1_cleanup (void)
 {

     if (LOGDEBUG) {
         log_debug("\nEnter y_operdata1_cleanup");
     }


     agt_cb_unregister_callbacks(
         y_op1_M_op1,
         (const xmlChar *)"/op1:stats");

     agt_cb_unregister_callbacks(
         y_op1_M_op1,
         (const xmlChar *)"/op1:stats/op1:port");

     agt_cb_unregister_callbacks(
         y_op1_M_op1,
         (const xmlChar *)"/op1:stats/op1:port/op1:data-errors");
     u_operdata1_cleanup();

 } /* y_operdata1_cleanup */

User H File: u_operdata1.h

This file can usually be replaced, as shown in previous sections. The relevant edits are shown below for completeness, but this file does not usually need to be preserved, unless it has been edited.

Add A New Typedef Struct to the Data Node Struct Typedefs

  • The y_op1_T_data_errors struct typedef for the data-errors container is created.

  • This struct is added to the struct for the parent 'port' in the y_op1_T_port struct

 /* container /stats/port/data-errors */
 typedef struct y_op1_T_data_errors_ {
     uint32 v_encode_fails;
     uint32 v_decode_fails;
     int64 v_error_rate;
 } y_op1_T_data_errors;

 /* list /stats/port */
 typedef struct y_op1_T_port_ {
     dlq_hdr_t qhdr;
     uint16 v_port_num;
     uint32 v_in_bytes;
     uint32 v_out_bytes;
     uint32 v_in_errors;
     uint32 v_out_errors;
     y_op1_T_data_errors v_data_errors;
 } y_op1_T_port;

 /* container /stats */
 typedef struct y_op1_T_stats_ {
     dlq_hdr_t v_port;
 } y_op1_T_stats;

Add a Function Prototype for the new GET2 Callback

The GET2 Callback is implemented in the User C File and invoked from the System C file. In this example, the u_op1_data_errors_get function prototype is added.

/**
 * @brief Get database object callback for container data-errors (getcb_fn2_t)\n
 * Path: container /stats/port/data-errors\n
 *
 * Fill in 'get2cb' response fields.
 *
 * @param get2cb GET2 control block for the callback.
 * @param k_op1_port_num Ancestor key leaf 'port-num' in list 'port'\n
 * Path: /op1:stats/op1:port/op1:port-num
 * @return return status of the callback.
 */
extern status_t u_op1_data_errors_get (
    getcb_get2_t *get2cb,
    uint16 k_op1_port_num);

User C File: u_operdata1.c

There will almost always be new code that must be merged into the existing User C file. In this example, the module header comment will be changed. That edit is not shown here.

Add a Function Definition for the new GET2 Callback

The GET2 Callback is added to this file. It must be filled in and implemented as described in the GET2 Callback Function section.

In this example, the u_op1_data_errors_get function definition is added to the file.

/**
 * @brief Get database object callback for container data-errors (getcb_fn2_t)\n
 * Path: container /stats/port/data-errors\n
 *
 * Fill in 'get2cb' response fields.
 *
 * @param get2cb GET2 control block for the callback.
 * @param k_op1_port_num Ancestor key leaf 'port-num' in list 'port'\n
 * Path: /op1:stats/op1:port/op1:port-num
 * @return return status of the callback.
 */
status_t u_op1_data_errors_get (
    getcb_get2_t *get2cb,
    uint16 k_op1_port_num)
{

    if (LOGDEBUG) {
        log_debug("\nEnter u_op1_data_errors_get");
    }

    /* check the callback mode type */
    getcb_mode_t cbmode = GETCB_GET2_CBMODE(get2cb);
    switch (cbmode) {
    case GETCB_GET_VALUE:
        break;
    case GETCB_GETNEXT_VALUE:
        return ERR_NCX_NO_INSTANCE;
    default:
        /* USE SET_ERROR FOR PROGRAMMING BUGS ONLY */
        return SET_ERROR(ERR_INTERNAL_VAL);
    }

    /* an NP container always exists so no test for node_exists
     * by the SIL or SIL-SA callback is needed
     */
    /* optional: check if any content-match nodes are present */
    boolean match_test_done = FALSE;
    val_value_t *match_val = GETCB_GET2_FIRST_MATCH(get2cb);
    for (; match_val; match_val =
        GETCB_GET2_NEXT_MATCH(get2cb, match_val)) {

        /**** CHECK CONTENT NODES AGAINST THIS ENTRY ***/

    }
    GETCB_GET2_MATCH_TEST_DONE(get2cb) = match_test_done;

    obj_template_t *obj = GETCB_GET2_OBJ(get2cb);
    status_t res = NO_ERR;

    /* go through all the requested terminal child objects */
    obj_template_t *childobj =
        getcb_first_requested_child(get2cb, obj);
    for (; childobj; childobj =
        getcb_next_requested_child(get2cb, childobj)) {

        const xmlChar *name = obj_get_name(childobj);

        /* Retrieve the value of this terminal node and
         * add with getcb_add_return_val */

        if (!xml_strcmp(name, y_op1_N_encode_fails)) {
            /* leaf encode-fails (uint32) */

        } else if (!xml_strcmp(name, y_op1_N_decode_fails)) {
            /* leaf decode-fails (uint32) */

        } else if (!xml_strcmp(name, y_op1_N_error_rate)) {
            /* leaf error-rate (decimal64) */

        }
    }

    return res;

} /* u_op1_data_errors_get */

Adding a New Leaf to an Existing List

This example shows the code changes that would occur if a new simple schema node (leaf or leaf-list) is addedd to an existing parent schema node.

Note

A new GET2 callback is NOT added for a leaf or leaf-list in most cases. If the parent schema node is a config=true node then a GET2 callback for a leaf or leaf-list is created.

In this case the in-drops and out-drops leafs are added to the existing port list, in the module 'operdata1.yang'.

   container stats {
     config false;

     list port {
       key port-num;
       leaf port-num { type uint16; }
       leaf in-bytes { type yang:counter32; }
       leaf out-bytes { type yang:counter32; }
       leaf in-errors { type yang:counter32; }
       leaf out-errors { type yang:counter32; }
       leaf in-drops { type yang:counter32; }
       leaf out-drops { type yang:counter32; }

       container data-errors {
         leaf encode-fails { type yang:counter32; }
         leaf decode-fails { type yang:counter32; }
         leaf error-rate {
           type decimal64 { fraction-digits 4; }
         }
       }
     }
   }

The differences in each of the 4 source files are explained:

System H File: y_operdata1.h (2)

This file can simply be replaced, as shown in the previous section. The same changes are made (i.e., revision-date and parameter name constants).

#define y_op1_N_in_drops (const xmlChar *)"in-drops"
#define y_op1_N_out_drops (const xmlChar *)"out-drops"

System C File: y_operdata1.c (2)

This file can simply be replaced. In this example, the module header comment will be changed. That is the only edit, and it is not shown here.

User H File: u_operdata1.h (2)

This file can usually be replaced, as shown in previous sections. The relevant edits are shown below for completeness, but this file does not usually need to be preserved, unless it has been edited.

Add Struct Members for the New Leafs In the Existing Struct Typedef

In this example, the v_in_drops and v_out_drops members are added to the y_op1_T_port struct.

 /* list /stats/port */
 typedef struct y_op1_T_port_ {
     dlq_hdr_t qhdr;
     uint16 v_port_num;
     uint32 v_in_bytes;
     uint32 v_out_bytes;
     uint32 v_in_errors;
     uint32 v_out_errors;
     uint32 v_in_drops;
     uint32 v_out_drops;
     y_op1_T_data_errors v_data_errors;
 } y_op1_T_port;

User C File: u_operdata1.c (2)

There will almost always be new code that must be merged into the existing User C file. In this example, the module header comment will be changed. That edit is not shown here.

Add Statements in the GET2 Callback for the New Leafs

The auto-generated GET2 callback includes a loop that iterates through each requested object. This loop needs to check for the new leafs that have been added to the parent schema node.

In this example, the in-drops and out-drops leafs are checked in this loop in the existing u_op1_port_get function.

 /* go through all the requested terminal child objects */
 obj_template_t *childobj =
     getcb_first_requested_child(get2cb, obj);
 for (; childobj; childobj =
     getcb_next_requested_child(get2cb, childobj)) {

     const xmlChar *name = obj_get_name(childobj);

     /* Retrieve the value of this terminal node and
      * add with getcb_add_return_val */

     if (!xml_strcmp(name, y_op1_N_in_bytes)) {
         /* leaf in-bytes (uint32) */

     } else if (!xml_strcmp(name, y_op1_N_out_bytes)) {
         /* leaf out-bytes (uint32) */

     } else if (!xml_strcmp(name, y_op1_N_in_errors)) {
         /* leaf in-errors (uint32) */

     } else if (!xml_strcmp(name, y_op1_N_out_errors)) {
         /* leaf out-errors (uint32) */

     } else if (!xml_strcmp(name, y_op1_N_in_drops)) {
         /* leaf in-drops (uint32) */

     } else if (!xml_strcmp(name, y_op1_N_out_drops)) {
         /* leaf out-drops (uint32) */

     }
 }

Adding A New Module to a Bundle

This section demonstrates the code changes that are required to add an additional module to a bundle.

In this example, the initial bundle ifbundle was created with the following command:

>  make_sil_bundle ifbundle ietf-interfaces iana-if-type ietf-system
  • To add new modules to a bundle, the bundle should be regenerated with the additional module (e.g., ietf-ip.yang)

> make_sil_bundle ifbundle ietf-interfaces iana-if-type ietf-system ietf-ip

Unchanged Bundle Files

The unaffected files are the individual module files for each existing module.

Unchanged System H Files

There are 3 unaffected System H files where only the module comment header has changed. These files can safely be replaced by the new revisions, unless these files have been edited.

  • y_iana-if-type.h

  • y_ietf-interfaces.h

  • y_ietf-system.h

Unchanged System C Files

There are 3 unaffected files where only the module comment header has changed, and include statements for the 'ietf-ip' H files are added. These include files are not actually used in the C file so they are not required. These files can safely be replaced by the new revisions, unless these files have been edited.

  • y_iana-if-type.c

  • y_ietf-interfaces.c

  • y_ietf-system.c

#include "y_ietf-ip.h"
#include "u_ietf-ip.h"

Unchanged User H Files

There are 3 unaffected User H files where only the module comment header and include statement for the 'ietf-ip' H files has changed. These files can safely be replaced by the new revisions, unless these files have been edited.

  • u_iana-if-type.h

  • u_ietf-interfaces.h

  • u_ietf-system.h

#include "u_ietf-ip.h"

Unchanged User C Files

There are 3 unaffected files where only the module comment header has changed, and include statements for the 'ietf-ip' H files are added. These include files are not actually used in the C file so they are not required. These files can safely be replaced by the new revisions, unless these files have been edited.

  • u_iana-if-type.c

  • u_ietf-interfaces.c

  • u_ietf-system.c

#include "y_ietf-ip.h"
#include "u_ietf-ip.h"

Added Bundle Files

A set of 4 modules is added for the new 'ietf-ip' module.

  • y_ietf-ip.h

  • y_ietf-ip.c

  • u_ietf-ip.h

  • u_ietf-ip.c

The User C file will need to be filled in like any other new SIL or SIL-SA file. The other files should not need any editing at all.

Changed Bundle Files

There are 2 'bundle' files which contain the wrapper API callbacks for the bundle (i.e., init, init2, and callback callbacks),

  • y_ifbundle.h

  • y_ifbundle.c

These System files do not need to be edited at all. The example edits to the bundle files are shown here to highlight the changes.

Bundle H File Changes

The module header comment is updated. No other changes are made.

Bundle C File Changes

  • The module header comment is updated. All of the short name assignments for the ietf-ip module are added to the comment.

  • The new H files are included.

    • The System H file is required.

    • The User H File is optional.

      #include "y_ietf-ip.h"
      #include "u_ietf-ip.h"
      
  • The 'ietf-ip' init, init2, and cleanup callbacks are invoked.

Updated Init Function: y_ifbundle_init

 /**
  * @brief Phase 1: Initialize the ifbundle server instrumentation library.
  *
  * Called by server when module is loaded.
  *
  * @param modname requested module name to load
  * @param revision requested revision date of the module to load.
  * This may be NULL if the module has no revision statements.
  * @return return status. An error will cause the module load to fail.
  */
 status_t y_ifbundle_init (
     const xmlChar *modname,
     const xmlChar *revision)
 {
     status_t res = NO_ERR;

     if (LOGDEBUG) {
         log_debug("\nEnter y_ifbundle_init");
     }


     /* change if custom handling done */
     if (xml_strcmp(modname, y_ifbundle_M_ifbundle)) {
         return ERR_NCX_UNKNOWN_MODULE;
     }

     res = y_ietf_interfaces_init(y_if_M_if, y_if_R_if);
     if (res != NO_ERR) {
         return res;
     }

     res = y_iana_if_type_init(y_ianaift_M_ianaift, y_ianaift_R_ianaift);
     if (res != NO_ERR) {
         return res;
     }

     res = y_ietf_system_init(y_sys_M_sys, y_sys_R_sys);
     if (res != NO_ERR) {
         return res;
     }

     res = y_ietf_ip_init(y_ip_M_ip, y_ip_R_ip);
     if (res != NO_ERR) {
         return res;
     }

     return res;

 } /* y_ifbundle_init */

Updated Init2 Function: y_ifbundle_init2

 /**
  * @brief Phase 2: Initialize the ifbundle server instrumentation library.
  *
  * SIL init phase 2: non-config data structures.
  * Called after running config is loaded.
  *
  * @return return status. An error will cause the
  * server initialization to fail.
  */
 status_t y_ifbundle_init2 (void)
 {
     status_t res = NO_ERR;

     if (LOGDEBUG) {
         log_debug("\nEnter y_ifbundle_init2");
     }


     res = y_ietf_interfaces_init2();
     if (res != NO_ERR) {
         return res;
     }

     res = y_iana_if_type_init2();
     if (res != NO_ERR) {
         return res;
     }

     res = y_ietf_system_init2();
     if (res != NO_ERR) {
         return res;
     }

     res = y_ietf_ip_init2();
     if (res != NO_ERR) {
         return res;
     }

     return res;

 } /* y_ifbundle_init2 */

Updated Cleanup Function: y_ifbundle_cleanup

 /**
  * @brief Cleanup the ifbundle server instrumentation library.
  *
  * Called by server when module is unloaded.
  *
  */
 void y_ifbundle_cleanup (void)
 {

     if (LOGDEBUG) {
         log_debug("\nEnter y_ifbundle_cleanup");
     }


     y_ietf_interfaces_cleanup();
     y_iana_if_type_cleanup();
     y_ietf_system_cleanup();
     y_ietf_ip_cleanup();

 } /* y_ifbundle_cleanup */