Database Operations
The server database is designed so that the SIL callback functions do not need to really know which database model is being used by the server (E.g., target is <candidate> vs. <running> configuration).
There are three SIL database edit callback phases:
Validate: Check the parameters no matter what database is the target
Apply: The server will manipulate the database nodes as needed. The SIL usually has nothing to do in this phase unless internal resources need to be reserved.
Commit or Rollback: Depending on the result of the previous phases, either the commit or the rollback callback phase will be invoked, if and when the changes are going to be finalized in the running configuration.
The SIL code is not responsible for maintaining the value tree for any database. This is done by the server.
The SIL database edit callback code is responsible for the following tasks:
Perform any data-model specific validation that is not already covered by a machine-readable statement, during the validation phase.
Reserve any data-model specific resources for the proposed new configuration content, during the apply phase.
Activate any data-model behavior changes based on the new configuration content, during the commit phase.
Release any reserved resources that were previously allocated in the apply phase, during the rollback phase.
The YANG "config" statement is used to identify configuration data nodes in NETCONF and RESTCONF.
If config='true':
the data nodes can be edited, and supports EDIT1, EDIT2 and EDIT3 callbacks
if NMDA enabled, the operational data nodes can be retrieved with GET2 callbacks
the data can be retrieved by a NETCONF client with these operations:
If config='false':
the data nodes cannot be edited, and supports GET1 or GET2 callbacks
the data can be retrieved by a NETCONF client with these operations:
Example YANG module:
container interfaces {
list interface {
key "name";
leaf name {
type string;
}
leaf-list untagged-ports {
type string;
}
leaf speed {
type enumeration {
enum 10m;
enum 100m;
enum auto;
}
}
leaf observed-speed {
config false;
type uint32;
}
}
}
leaf version {
config false;
type string;
}
Each callback type has its own callback API function prototype, and each callback has its own register and unregister functions that need to be used. This code is usually generated automatically by yangdump-pro.
The following database APIs are described in this section:
EDIT1 Callback : Deprecated First Generation EDIT Callback
EDIT2 Callback : Second Generation EDIT Callback
EDIT3 Callback : Latest Generation EDIT Callback
Global EDIT Callbacks : Global Callbacks for EDIT operations
Additional Methods to Register EDIT Callbacks : Additional methods to register EDIT2 and EDIT3 callbacks with extended functionality.
Edit Callback Overview
Getting the Current Edit Transaction ID
In order to determine if an edit transaction is in progress, an API can be used. If the transaction-id returned by this function is zero, then no transaction is in progress.
-
ncx_transaction_id_t agt_cfg_txid_in_progress(ncx_cfg_t cfgid)
Return the ID of the current transaction ID in progress.
- Parameters:
cfgid -- config ID to check
- Returns:
txid of transaction in progress or 0 if none
Example: Get the current transaction ID for the running datastore
ncx_transaction_id_t txid = agt_cfg_txid_in_progress(NCX_CFGID_RUNNING);
Deletion of Child Nodes (sil-delete-children-first)
If the client deletes a container or list entry, then the server will normally only invoke the SIL callback for deletion for this node, even if there are child lists and/or containers. For example, if the client deletes a container of list entries, then the server will normally only invoke the SIL callback for deletion for the container.
It may be easier to allow the server to use the SIL callbacks for the child list nodes, to delete a data structure “bottom-up”.
Consider this example YANG data model:
container top {
list list1 {
key i;
leaf i { type string; }
list list2 {
key j;
leaf j { type string; }
}
}
}
If container 'top' is deleted, and the sil-delete-children-first parameter is set to 'true':
SIL callbacks for any 'list2' list entries
SIL callbacks for any 'list1' list entries
SIL callback for the 'top' container
If container 'top' is deleted, and the sil-delete-children-first parameter is set to 'false':
SIL callback for the 'top' container
There is a CLI parameter for global control of this behavior:
There are 2 YANG extensions that can be used with (or instead) of the CLI parameter:
ncx:sil-delete-children-first: force the server to delete all the child nodes before deleting the parent container
ncx:no-sil-delete-children-first: force the server to not delete all the child nodes before deleting the parent container
These extensions can be used in any parent node (container or list) and can be used to control the server SIL callback behavior for any child node (container, list, leaf, leaf-list, anyxml).
Determining the Real Deletion Node
Introduced: 24.10-3
If the --sil-delete-children-first='true' mode is used, then it may be useful to know if a SIL callback is for the top-most deleted node, or a descendant node being deleted first.
For the example above, this flag would be 'true' for the SIL callbacks for 'list2' and 'list1' entries, and 'false' for the 'top' container.
For EDIT3 Callback functions:
Use the 'in_delete_children_first` flag in the editcb.
Refer to the Accessing EDIT3 Control Block section.
// in agt/agt_editcb.h boolean in_delete_children_first; // in SIL callback code boolean is_nested = editcb->in_delete_children_first;
For EDIT1 Callback and EDIT2 Callback functions:
Use the 'RPC_MSG_IN_DELETE_CHILDREN_FIRST' macro
Refer to the RPC Message Header section.
// in ncx/rpc.h #define RPC_MSG_IN_DELETE_CHILDREN_FIRST(MSG) \ (MSG)->rpc_in_delete_children_first // in SIL callback code boolean is_nested = RPC_MSG_IN_DELETE_CHILDREN_FIRST(msg);
Database Edit Validate Callback Phase
A SIL database validation phase callback function is responsible for checking all the 'description statement' sort of data model requirements that are not covered by any of the YANG machine-readable statements.
For example, if a 'user name' parameter needed to match an existing user
name in /etc/passwd
then the SIL validation callback would call
the system APIs needed to check if the 'newval' string value matched a
valid user name. The server will make sure the user name is well-formed
and could be a valid user name.
Database Edit Apply Callback Phase
The callback function for this phase is called when database edits are being applied to the running configuration. The resources needed for the requested operation may be reserved at this time, if needed.
Database Edit Commit Callback Phase
This callback function for this phase is called when database edits are being committed to the running configuration. The SIL callback function is expected to finalize and apply any data-model dependent system behavior at this time.
Database Edit Rollback Callback Phase
This callback function for this phase is called when database edits are being undone, after some apply phase or commit phase callback function returned an error, or a confirmed commit operation timed out.
SIL Callback Function Summary
The SIL callback function is expected to release any resources it allocated during the apply or commit phases. Usually only the commit or the rollback function will be called for a given SIL callback, but it is possible for both to be called. For example, if the 'rollback-on-error' option is in effect, and some SIL commit callback fails after your SIL commit callback succeeds, then your SIL rollback callback may be called as well.
The server supports 3 modes of database editing callbacks.
The original mode (EDIT1) is designed to invoke data node callbacks at the leaf level. This means that each altered leaf will cause a separate SIL callback. If no leaf callbacks are present, then the parent node will be invoked multiple times.
The EDIT2 mode is "list-based" or "container-based" instead. It does not require SIL callbacks for terminal nodes and the server calls the parent SIL callback only once for multiple child node alterations. Refer to EDIT2 Callback.
The EDIT3 mode is similar to EDIT2 callback mode; however, it provides 'update' value to update your device data along with 'newval' and 'curval'. The 'update' value streamlines the update process by providing a composite 'update' value, that incorporates latest changes proposed by the user (newval) along with relevant current values from the datastore (curval). The device instrumentation technique in this callback is to replace the existing data with this composite 'update' value. Refer to EDIT3 Callback.
EDIT1 Callback
First generation edit callbacks, generated by yangdump-pro using node-based APIs. An edit function is generated for every node, including terminal nodes (leaf, leaf-list, anyxml, anydata). An EDIT2 and EDIT3 callbacks handle all the terminal nodes within the parent function instead of separate functions for each node.
New SIL or SIL-SA code should use EDIT2 or EDIT3 callbacks instead.
EDIT1 Callback Function
The same callback template is used for EDIT1 and EDIT2 callbacks:
-
typedef status_t (*agt_cb_fn_t)(ses_cb_t *scb, rpc_msg_t *msg, agt_cbtyp_t cbtyp, op_editop_t editop, val_value_t *newval, val_value_t *curval)
EDIT1 and EDIT2 Callback function for server object handler.
Used to provide a callback sub-mode for a specific named object
- Param scb:
session control block making the request
- Param msg:
incoming rpc_msg_t in progress
- Param cbtyp:
reason for the 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:
status
The 'scb' parameter represents a session control block
structure that is defined in the ncx/ses.h
. This control
block is primarily used for error reporting, as described in the example
section later. However, can be used for more advanced actions. It
provides access to the session specific information, such as current
message input/output encoding, current session ID information, current
protocol information, user name information, peer address information,
etc. Note, almost all the fields in this structure should NOT be changed
and accessed directly. This control block ideally should be used only
for getting more information about the current session, not for
alteration of any of its fields.
The 'msg' parameter represents the NETCONF Server and Client RPC Request/Reply Message Handler control block that is defined in the netconf/src/ncx/rpc.h. Similarly to SCB, this control block is primarily used for error reporting, as described in the example section later. The fields of this control block should NOT be access and changed for other purposes.
The 'cbtype' parameter represents an enumeration structure of the different server EDIT callback types that is defined in the netconf/src/agt/agt.h. This control block specifies what Phase is in the process, Validation, Apply, or Commit/Rollback, as described in the example section later.
The 'editop' parameter represents an enumeration structure of the NETCONF <edit-config> operation types that is defined in the netconf/src/ncx/op.h. This control block specifies what operation is in the process, merge, replace, delete, or other, as described in the example section later.
The 'newval' and 'curval' parameters represent data nodes that are being edited. The example section demonstrates how they can be utilized.
EDIT1 Callback Registration and Cleanup
-
status_t agt_cb_register_callback(const xmlChar *modname, const xmlChar *defpath, const xmlChar *version, agt_cb_fn_t cbfn)
Register an object specific edit callback function use the same fn for all callback phases all phases will be invoked.
- 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 for all callback phases
- Returns:
status
EDIT1 Callback Registration
The following example shows an EDIT1 callback registration.
#define EXAMPLE_MODNAME (const xmlChar *)"ietf-interfaces-example"
#define EXAMPLE_VERSION (const xmlChar *)"2017-01-01"
#define EXAMPLE_DEFPATH (const xmlChar *)"/if:interfaces/if:interface"
/********************************************************************
* FUNCTION interfaces_init
*
* initialize the server instrumentation library.
* Initialization Phase 1
*
*********************************************************************/
static status_t
interfaces_init (void)
{
status_t res =
agt_cb_register_callback(EXAMPLE_MODNAME,
EXAMPLE_DEFPATH,
EXAMPLE_VERSION,
edit_callback_example);
return res;
}
EDIT1 Callback Cleanup
Use the 'agt_cb_unregister_callbacks' function to unregister all callbacks for the data node.
EDIT1 Callback Function Example
In this example, the callback code forces Rollback Phase if a new value of an “interface” list is not acceptable. Otherwise an agent can process to the next step and run device instrumentation as required.
A new list validation, in this example, is done during “commit” phase. A new value is already written to the datastore (value is getting written during apply phase) which means the server will have to reverse the edit. The server will automatically delete just created new list element from the datastore and restore the state to the initial state, to the state before the edit.
/********************************************************************
* FUNCTION edit1_callback_example
*
* Callback function for server object handler
* Used to provide a callback sub-mode for
* a specific named object
*
* Path: /interfaces/interface
* Add object instrumentation in COMMIT phase.
*
********************************************************************/
static status_t
edit1_callback_example (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;
const xmlChar *errorstr = (errorval) ? NCX_NT_VAL : NCX_NT_NONE;
/* try to find a key value of the /interfaces list to validate */
val_value_t *child_val = NULL;
if (newval) {
child_val =
val_find_child(newval,
EXAMPLE_MODNAME,
(const xmlChar *)"name");
if (child_val && typ_is_string(VAL_BTYPE(child_val))) {
log_info("\ncallback for %s editop, test child name=%s",
op_editop_name(editop), VAL_STR(child_val));
}
}
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:
interface_enabled = TRUE;
break;
case OP_EDITOP_MERGE:
break;
case OP_EDITOP_REPLACE:
break;
case OP_EDITOP_CREATE:
interface_enabled = TRUE;
/* Force Rollback if the key value is not acceptable */
if (newval && child_val && typ_is_string(VAL_BTYPE(child_val)) &&
!xml_strcmp(VAL_STR(child_val), (const xmlChar *)"not-supported")) {
log_info("\nKey value is not supported for %s editop, name=%s",
op_editop_name(editop), VAL_STR(child_val));
/* Validation failed if a key value is not supported */
errorval = child_val;
res = ERR_NCX_OPERATION_NOT_SUPPORTED;
} else {
/* Run device instrumentation here */
}
break;
case OP_EDITOP_DELETE:
interface_enabled = FALSE;
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 error: set the res, errorstr, and errorval parms */
if (res != NO_ERR) {
agt_record_error(scb,
&msg->mhdr,
NCX_LAYER_CONTENT,
res,
NULL,
NCX_NT_STRING,
errorstr,
NCX_NT_VAL,
errorval);
}
return res;
} /* edit_callback_example */
The following part of the above code example is used to find the “name” key node of the edited “interface” list.
-
val_value_t *val_find_child(const val_value_t *parent, const xmlChar *modname, const xmlChar *childname)
Find the first instance of the specified child node.
- Parameters:
parent -- parent complex type to check
modname --
module name; the first match in this module namespace will be returned
NULL: the first match in any namespace will be returned;
childname -- name of child node to find
- Returns:
pointer to the child if found or NULL if not found
-
boolean typ_is_string(ncx_btype_t btyp)
Check if the base type is a simple string (not list)
- Parameters:
btyp -- base type enum to check
- Returns:
TRUE if base type is textual
FALSE if some other type
val_value_t *child_val = NULL;
if (newval) {
child_val =
val_find_child(newval,
EXAMPLE_MODNAME,
(const xmlChar *)"name");
if (child_val && typ_is_string(VAL_BTYPE(child_val))) {
log_info("\ncallback for %s editop, test child name=%s",
op_editop_name(editop), VAL_STR(child_val));
}
}
The 'val_find_child' API function finds the specified child node. Alternatively, 'val_find_child_fast', 'val_find_child_obj', 'val_find_child_que' API functions could be used to retrieve the desired value.
The 'typ_is_string' API function checks if the base type is a simple string to use this string later for logging. Alternatively, 'typ_is_enum', 'typ_is_number' API functions could be used to check the desired type.
The 'VAL_BTYPE()' and 'VAL_STR()' macros are used to access val_value_t structure and get the information about its base type and get string value. If the type of the value would be an integer, for example, VAL_INT32, VAL_UINT16, etc. macros could be used to retrieve the actual set value of it.
In the following part of the above code example the
previously retrieved key value is validated.
If the provided in the <edit-config> key value is not acceptable as specified below,
then the '*res' return status will be set to ERR_NCX_OPERATION_NOT_SUPPORTED
enumeration value, that would signal to record an error and rollback the
<edit-config> operation.
/* Force Rollback if the key value is not acceptable */
if (newval && child_val && typ_is_string(VAL_BTYPE(child_val)) &&
!xml_strcmp(VAL_STR(child_val), (const xmlChar *)"not-supported")) {
log_info("\nKey value is not supported for %s editop, name=%s",
op_editop_name(editop), VAL_STR(child_val));
/* Validation failed if a key value is “not-supported” */
errorval = child_val;
res = ERR_NCX_OPERATION_NOT_SUPPORTED;
} else {
/* Run device instrumentation here */
}
EDIT2 Callback
The same callback template is used for EDIT1 and EDIT2 callbacks. The difference is how terminal child nodes are handled. Second generation callbacks are generated by yangdump-pro for container or list-based APIs. An edit function is generated for “parent” list and container nodes only. The terminal child nodes are handled by this callback.
Each nested container or list has its own callback. This is generated in yangdump-pro or 'make_sil_*' scripts using the --sil-edit2 parameter. The same callback function signature as EDIT1 is used, but the registration function and procedure is different.
The following key aspects define EDIT2 callbacks:
In this mode there are no SIL callback functions expected for terminal nodes (leaf, leaf-list, anyxml).
The SIL callback for a container or list will be invoked, even if only child terminal nodes are being changed.
The parent SIL callback will be invoked only once (per phase) if multiple child nodes are being altered at once.
The parent node for edits where child nodes are being altered are flagged in the
undo
record as a special "edit2_merge". The edit operation will beOP_EDITOP_MERGE
in this case, but the parent node is not being changed.The special “edit2_merge" type of edit will have a queue of child_undo records containing info on the child edits. For example, 1 leaf could be created, another leaf modified, and a third leaf deleted, all in the same edit request. The 'child_undo' records provide the edit operation and values being changed.
For the Global version of this EDIT2 callback, see Global EDIT Callbacks.
EDIT2 Callback Function
The EDIT2 callback uses the same function as the EDIT1 Callback Function.
EDIT2 Callback Initialization and Cleanup
The EDIT2 callback function is hooked into the server with the 'agt_cb_register_edit2_callback' function, described below. The SIL code generated by yangdump-pro uses this function to register a single callback function for all callback phases.
For more advanced registration options, see Additional Methods to Register EDIT Callbacks.
The registration is done during the Initialization Phase 1 before the startup configuration is loaded into the running configuration database and before running configurations are loaded.
-
status_t agt_cb_register_edit2_callback(const xmlChar *modname, const xmlChar *defpath, const xmlChar *version, agt_cb_fn_t cbfn)
Register an object specific edit2 callback function.
Use the same fn for all callback phases all phases will be invoked
Only Callbacks for containers and lists are allowed in edit2 mode; Top Level Terminal Nodes are NOT SUPPORTED in edit2 mode
- 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 for all callback phases
- Returns:
status
EDIT2 Callback Registration
static status_t interfaces_init (void)
{
status_t res =
agt_cb_register_edit2_callback(EXAMPLE_MODNAME,
EXAMPLE_DEFPATH,
EXAMPLE_VERSION,
edit2_callback_example);
return res;
}
EDIT2 Callback Cleanup
Use the 'agt_cb_unregister_callbacks' function to unregister all callbacks for the data node.
The unregister function needs to be called just once for a specific object. It will unregister EDIT1, EDIT2, EDIT3 and GET2 callbacks for the object. However, if it is called multiple times for the same object, it will return with NO errors.
All other callbacks that the object may hold should be unregistered separately.
EDIT2 Callback Function Example
The EDIT2 callback template is the same as the EDIT1 callback. The difference is that there is a queue of child edit records that may need to be accessed to reliably process the edit requests.
In the following example, EDIT2 callback code checks each 'child_undo' record in order:
/********************************************************************
* FUNCTION edit2_callback_example
*
* Callback function for server object handler
* Used to provide a callback sub-mode for
* a specific named object
*
* Path: /interfaces/interface
* Add object instrumentation in COMMIT phase.
*
********************************************************************/
static
edit2_callback_example (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;
if (LOGDEBUG) {
log_debug("\nEnter edit2_callback_example callback for %s phase",
agt_cbtype_name(cbtyp));
}
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:
/* the edit is not really on this node; need to get
* each child_undo record to get the real edited nodes
* and the edited operations
*/
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;
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 == NULL) {
return ERR_INTERNAL_MEM;
}
}
if (child_curval) {
curval_str = val_make_sprintf_string(child_curval);
if (curval_str == NULL) {
m__free(newval_str);
return ERR_INTERNAL_MEM;
}
}
log_info("\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);
/* Force Rollback if the child value is not acceptable */
if (child_newval &&
!xml_strcmp(VAL_NAME(child_newval),
(const xmlChar *)"untagged-ports") &&
!xml_strcmp(VAL_STR(child_newval),
(const xmlChar *)"not-supported")) {
res = ERR_NCX_OPERATION_NOT_SUPPORTED;
m__free(newval_str);
m__free(curval_str);
break;
}
/**** process child edits here ****/
m__free(newval_str);
m__free(curval_str);
child_edit = agt_cfg_next_child_edit(child_edit);
}
break;
case OP_EDITOP_REPLACE:
case OP_EDITOP_CREATE:
/* the edit is on this list node and the child editop
* can be treated the same as the parent (even if different)
* the val_value_t child nodes can be accessed and there
* are no child_undo records to use
*/
val_value_t *child_newval = NULL;
child_newval =
val_find_child(newval,
EXAMPLE_MODNAME,
(const xmlChar *)"untagged-ports");
val_value_t *leaflist_val = child_newval;
while (leaflist_val) {
/**** process child leaf-list edits here ****/
leaflist_val = val_next_child_same(leaflist_val);
}
/**** process other child edits here if needed ****/
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) {
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;
} /* edit2_callback_example */
-
const xmlChar *agt_cbtype_name(agt_cbtyp_t cbtyp)
Get the string for the server callback phase.
- Parameters:
cbtyp -- callback type enum
- Returns:
const string for this enum
Accessing Child Node Edits
If the operation is “merge”, the edit is not really on the container or list node. The callback needs to get each child_undo record to get the real edited nodes and the edit operations.
If the parent operation is “merge” and the actual edit is on the node with default value. The edited node has a “default” YANG statement, then the actual child edit operation will always be “merge”. The difference will be in the newval, curval values.
create default node |
new non-default value |
default value |
modify default node |
new non-default or set back to default value |
old non-default value |
delete default node |
default value |
old non-default value |
The following code demonstrates how to loop through the child undo records and retrieve the actual operation and the actual child nodes.
case OP_EDITOP_MERGE:
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;
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 == NULL) {
return ERR_INTERNAL_MEM;
}
}
if (child_curval) {
curval_str = val_make_sprintf_string(child_curval);
if (curval_str == NULL) {
m__free(newval_str);
return ERR_INTERNAL_MEM;
}
}
log_info("\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);
/* Force Rollback if the child value is not acceptable */
if (child_newval &&
!xml_strcmp(VAL_NAME(child_newval),
(const xmlChar *)"untagged-ports") &&
!xml_strcmp(VAL_STR(child_newval),
(const xmlChar *)"not-supported")) {
res = ERR_NCX_OPERATION_NOT_SUPPORTED;
m__free(newval_str);
m__free(curval_str);
break;
}
/**** process child edits here ****/
m__free(newval_str);
m__free(curval_str);
child_edit = agt_cfg_next_child_edit(child_edit);
}
break;
-
agt_cfg_undo_rec_t *agt_cfg_first_child_edit(agt_cfg_transaction_t *txcb, val_value_t *newnode, val_value_t *curnode)
Get the first child node edit record for a given transaction.
- Parameters:
txcb -- transaction control block to clean
newnode -- new value node passed to callback
curnode -- current value node passed to callback
- Returns:
pointer to the undo edit record for the first child node edit
NULL if none found
-
agt_cfg_undo_rec_t *agt_cfg_next_child_edit(agt_cfg_undo_rec_t *curedit)
Get the next child node edit record for a given transaction.
- Parameters:
curedit -- pointer to the current child edit
- Returns:
pointer to the undo edit record for the next child node edit
NULL if none found
-
void agt_cfg_child_edit_fields(agt_cfg_undo_rec_t *child_undo, op_editop_t *editop, val_value_t **newval, val_value_t **curval)
Get the child edit fields from the undo record.
- Parameters:
child_undo -- child undo record that was returned
editop -- [out] address of return editop
newval -- [out] address of return new value
curval -- [out] address of return current value
The following code illustrates how to loop through the children undo records in order to retrieve the actual operation and the actual edited children:
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);
/**** process child edits here ****/
child_edit = agt_cfg_next_child_edit(child_edit);
}
EDIT2 Callback For a Create Operation
-
val_value_t *val_next_child_same(val_value_t *curchild)
Get the next instance of the corresponding child node.
- Parameters:
curchild -- child type to find next instance of
- Returns:
pointer to the next child of same type or NULL if none
The following code shows how to access the 'untagged-ports' leaf-list child nodes that are getting created along with their “interface” parent:
case OP_EDITOP_CREATE:
val_value_t *child_newval = NULL;
child_newval =
val_find_child(newval,
EXAMPLE_MODNAME,
(const xmlChar *)"untagged-ports");
val_value_t *leaflist_val = child_newval;
while (leaflist_val) {
/**** process child leaf-list edits here ****/
leaflist_val = val_next_child_same(leaflist_val);
}
/**** process other child edits here if needed ****/
break;
If an edit operation creates a new or modifies an existing data node, or its children, the EDIT2 callback will be invoked and run.
If the edit operation creates a new list entry, the operation will be “create” and any children that are getting created along with its parent should be treated as regular nodes and should be accessed the same way as for EDIT1 callback.
If the operation is “merge”, the actual operation and edit is on children nodes, so the child edits should be checked and applied.
SIL-SA EDIT2 Callbacks
The SIL-SA EDIT2 callback usage is the same as the EDIT2 callback except EDIT2 MERGE handling. The difference is only in the children edits access APIs.
There are SIL-SA EDIT2 mode specific high-level transaction access
and management utilities in sil-sa/sil_sa.h
. These
functions access the lower-level functions to provide simpler functions
for common transaction management tasks.
The following table highlights available functions and SIL vs SIL-SA difference:
SIL-SA Function |
SIL Function |
Description |
---|---|---|
sil_sa_first_child_edit |
agt_cfg_first_child_edit |
Get the first child node edit record for a given transaction. |
sil_sa_next_child_edit |
agt_cfg_next_child_edit |
Get the next child node edit record for a given transaction. |
sil_sa_child_edit_fields |
agt_cfg_child_edit_fields |
Get the child edit fields from the specified undo record. |
-
sil_sa_child_edit_t *sil_sa_first_child_edit(rpc_msg_t *msg)
Get the first child edit from the transaction control block.
SIL-SA EDIT2 MODE
SIL-SA Analogue agt_cfg_first_child_edit() API. Get the first child node edit record
- Parameters:
msg -- rpc msg to use to find keys
- Returns:
pointer to the first child edit
-
sil_sa_child_edit_t *sil_sa_next_child_edit(sil_sa_child_edit_t *curedit)
Get the next child edit from the transaction control block.
SIL-SA EDIT2 MODE
SIL-SA Analogue agt_cfg_next_child_edit() API. Get the next child node edit
- Parameters:
curedit -- pointer to the current child edit
- Returns:
pointer to next child node edit
NULL if none found
-
void sil_sa_child_edit_fields(sil_sa_child_edit_t *child_edit, op_editop_t *editop, val_value_t **newval, val_value_t **curval)
Get the child edit fields.
SIL-SA EDIT2 MODE
SIL-SA Analogue agt_cfg_child_edit_fields() API. Get the child edit fields from the child_edit record
- Parameters:
child_edit -- child_edit record that was returned from the server
editop -- [out] address of return editop
newval -- [out] address of return new value
curval -- [out] address of return current value
SIL-SA EDIT2 Callback Function Example
In the following example, SIL-SA EDIT2 callback code gets each 'child edit' record in order to retrieve the real edited nodes.
/********************************************************************
* FUNCTION silsa_edit2_callback_example
*
* SIL-SA EDIT2 Callback function for server object handler
* Used to provide a callback sub-mode for
* a specific named object
*
* Path: /interfaces/interface
* Add object instrumentation in COMMIT phase.
*
********************************************************************/
static
silsa_edit2_callback_example (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;
if (LOGDEBUG) {
log_debug("\nEnter silsa_edit2_callback_example callback for %s phase",
agt_cbtype_name(cbtyp));
}
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:
/* the edit is not really on this node; need to get
* each child_undo record to get the real edited nodes
* and the edited operations
*/
sil_sa_child_edit_t *child_edit = sil_sa_first_child_edit(msg);
while (child_edit) {
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;
sil_sa_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 == NULL) {
return;
}
}
if (child_curval) {
curval_str = val_make_sprintf_string(child_curval);
if (curval_str == NULL) {
if (newval_str) {
m__free(newval_str);
}
return;
}
}
log_debug("\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);
m__free(newval_str);
m__free(curval_str);
child_edit = sil_sa_next_child_edit(child_edit);
}
break;
case OP_EDITOP_REPLACE:
case OP_EDITOP_CREATE:
/* the edit is on this list node and the child editop
* can be treated the same as the parent (even if different)
* the val_value_t child nodes can be accessed and there
* are no child_undo records to use
*/
val_value_t *child_newval = NULL;
child_newval =
val_find_child(newval,
EXAMPLE_MODNAME,
(const xmlChar *)"untagged-ports");
val_value_t *leaflist_val = child_newval;
while (leaflist_val) {
/**** process child leaf-list edits here ****/
leaflist_val = val_next_child_same(leaflist_val);
}
/**** process other child edits here if needed ****/
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) {
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;
} /* silsa_edit2_callback_example */
EDIT3 Callback
Note
This callback is available starting in version 23.10T-6.
The EDIT3 callbacks represent the latest advancement in server database editing. Building on the EDIT2 framework, EDIT3 introduces an 'update' value that combines the new proposed changes 'newval' from the user with the current values from the datastore 'curval'.
This composite 'update' value significantly streamlines the update process, offering a more efficient and developer-friendly approach to handling data edits.
Advantages of Using EDIT3 Callbacks:
Enhanced Efficiency: By providing a composite 'update' value, EDIT3 reduces the need for separate handling of 'newval' and 'curval', simplifying the callback implementation.
Developer Convenience: Eliminating the need for 'child_edit' as in EDIT2 callbacks, EDIT3 allows for a more straightforward and less error-prone coding experience.
Improved Data Handling: EDIT3 callbacks ensure that the most up-to-date and relevant data is always reflected, improving the accuracy and reliability of the database updates.
Note
EDIT3 does not utilize the same callback template as EDIT1 and EDIT2, nor does it use the same registration functions.
The difference from EDIT2 callbacks is in function parameters and the way callbacks is used now. The EDIT3 callback does not require to use 'newval' or 'curval' nor does it require to obtain child_undo records containing info on the child edits. Now all the device instrumentation can be based on the 'update' value.
Each nested container or list has its own callback. This is generated in yangdump-pro or 'make_sil_*' scripts using the --sil-edit3 parameter.
The following key aspects define EDIT3 callbacks:
In this mode there are no SIL callback functions expected for terminal nodes (leaf, leaf-list, anyxml).
The SIL callback for a container or list will be invoked, even if only child terminal nodes are being changed.
The parent SIL callback will be invoked only once (per phase) if multiple child nodes are being altered at once.
No more the special “edit2_merge" type. Now all the device instrumentation can be based on the 'update' value.
New Function parameter called 'editcb' - Edit Control Block that contains EDIT3 callback information.
The implementation of EDIT3 callbacks follows the same basic structure as EDIT1 and EDIT2 but with significant enhancements in handling the update process. During various operations like create, merge, replace, and delete, the 'update' value plays a pivotal role in determining the final state of the data in the database.
For the Global version of this EDIT3 callback, see Global EDIT Callbacks.
EDIT3 Callback Function
The EDIT3 callback uses a new template function:
-
typedef status_t (*agt_edit3_fn_t)(agt_editcb_t *editcb)
EDIT3 Callback function for server object handler.
Used to provide a callback sub-mode for a specific named complex object
- Param editcb:
Edit Control Block that contains full EDIT3 callback information
- Return:
status:
EDIT3 Callback Initialization and Cleanup
The registration and cleanup process for EDIT3 callbacks are similar to that of EDIT1 and EDIT2 callbacks.
The EDIT3 callback function is hooked into the server with the 'agt_cb_register_edit3_callback' function, described below. The SIL code generated by yangdump-pro uses this function to register a single callback function for all callback phases.
For more advanced registration options, see Additional Methods to Register EDIT Callbacks.
The registration is done during the Initialization Phase 1 before the startup configuration is loaded into the running configuration database and before running configurations are loaded.
-
status_t agt_cb_register_edit3_callback(const xmlChar *modname, const xmlChar *defpath, const xmlChar *version, agt_edit3_fn_t edit3_cbfn)
Register an object specific edit3 callback function.
Use the same fn for all callback phases all phases will be invoked
Only Callbacks for containers and lists are allowed in edit3 mode; Top Level Terminal Nodes are NOT SUPPORTED in edit3 mode
- 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
edit3_cbfn -- address of callback function to use for all callback phases
- Returns:
status
EDIT3 Callback Registration
static status_t interfaces_init (void)
{
status_t res =
agt_cb_register_edit3_callback(EXAMPLE_MODNAME,
EXAMPLE_DEFPATH,
EXAMPLE_VERSION,
edit3_callback_example);
return res;
}
EDIT3 Callback Cleanup
Use the 'agt_cb_unregister_callbacks' function to unregister all callbacks for the data node.
The unregister function needs to be called just once for a specific object. It will unregister EDIT1, EDIT2, EDIT3 and GET2 callbacks for the object. However, if it is called multiple times for the same object, it will return with NO errors.
All other callbacks that the object may hold should be unregistered separately.
EDIT3 Callback Function Example
An example implementation of an EDIT3 callback function demonstrates how the 'update' value is utilized in various editing operations.
The following code snippet provides an example of how to implement the EDIT3 callback in a practical scenario, specifically focusing on managing interface entries within a network device configuration context.
The u_if_interface_edit function serves as the main EDIT3 callback handler for interface edits. It is structured to manage different phases of the callback process, aligning actions with the specified operation type (editop).
Key functionalities of this function include:
Callback Information Handling: The function begins by extracting relevant information from the editcb control block
Handling Different Callback Types: Based on the cbtyp, the function executes different actions. For example, in the
AGT_CB_COMMIT
phase, it determines whether to invoke delete_interface or update_interface based on whether the edit operation is a delete or an update.Logging and Error Handling: The function includes provisions for logging and error handling, ensuring that each action taken is properly recorded and any issues are promptly addressed.
This example illustrates the practical application of the EDIT3 callback in a network configuration context. It showcases how the update value can be used to efficiently manage changes to the device’s configuration, either by updating existing entries or deleting them as required.
/*
* @brief Edit database object callback (agt_edit3_fn_t)\n
* Path: list /interfaces/interface
*
* @param editcb Edit Control Block that contains
* EDIT3 callback information
* @param k_if_name Local key leaf 'name' in list 'interface'\n
* Path: /if:interfaces/interface/name
* @return return status for the phase.
*/
status_t
u_if_interface_edit (agt_editcb_t *editcb,
const xmlChar *k_if_name)
{
status_t res = NO_ERR;
/* Callback information available in the callback */
ses_cb_t *scb = editcb->scb;
rpc_msg_t *msg = editcb->msg;
agt_cbtyp_t cbtyp = editcb->cbtyp;
op_editop_t editop = editcb->editop;
/* Node related information available in the callback */
val_value_t *newval = editcb->newval;
val_value_t *curval = editcb->curval;
val_value_t *update = editcb->update;
obj_template_t *obj = editcb->obj;
const xmlChar *modname = editcb->modname;
/* Transaction information available in the callback */
agt_cfg_transaction_t *txcb = editcb->txcb;
boolean isvalidate = editcb->isvalidate;
boolean isrunning = editcb->isrunning;
const xmlChar *user_id = editcb->user_id;
const xmlChar *client_addr = editcb->client_addr;
const xmlChar *target = editcb->target;
const xmlChar *txid_str = editcb->txid_str;
val_value_t *errorval = (curval) ? curval : newval;
if (LOGDEBUG) {
log_debug("\nEnter u_if_interface_edit callback for %s phase",
agt_cbtype_name(cbtyp));
}
switch (cbtyp) {
case AGT_CB_VALIDATE:
/* description-stmt validation here */
break;
case AGT_CB_APPLY:
/* database manipulation are performed by the server here */
break;
case AGT_CB_COMMIT:
/* device instrumentation done here */
if (op_editop_is_delete(editop)) {
/* Delete all data from the device */
res = delete_interface(k_if_name);
} else {
/* Use update value to update your device data
*
* The technique here is to replace the existing data
* with this composite Update value.
*/
res = update_interface(update, k_if_name);
}
break;
case AGT_CB_ROLLBACK:
/* undo device instrumentation done in Apply Phase */
break;
default:
FLAG_INT_ERROR;
res = ERR_INTERNAL_VAL;
}
return res;
} /* u_if_interface_edit */
EDIT3 Update Example
The update_interface function is a pivotal part of the EDIT3 callback mechanism, designed to modify an internal data structure representing a network interface.
This function performs the following key tasks:
Extracting Child Nodes: This is achieved by finding the child nodes within the update data structure.
Creating New Internal Structure: This step involves allocating and initializing a new data structure that mirrors the updated interface configuration.
Handling Existing Interfaces: If an existing interface is identified, this function replaces it with the newly created new_interface
Adding New Interfaces: In cases where no existing interface is found, the new interface is added as a new entry, expanding the internal data representation to include the updated configuration.
/*
* @brief Update the interface entry
*
* An example function to illustrated an update of an
* internal structure that represents the interface entry
*
* @param val val_value_t representing interface entry
* @param name == key leaf value for the entry to delete
* @return status
*/
static status_t
update_interface (val_value_t *update,
const xmlChar *name)
{
/* Get the description child node of the interface */
const xmlChar *descr_str = NULL;
val_value_t *descr =
val_find_child(update, NULL, NCX_EL_DESCRIPTION);
if (descr) {
descr = (const xmlChar *)VAL_STRING(descr);
}
/* Create a new internal structure based on the provided
* update val value and the name key.
*
* Create and fill the internal structure with information
* gathered from update value.
*/
certmap_t *new_interface =
make_new_interface(name, descr_str);
if (new_interface == NULL) {
return ERR_NCX_INVALID_VALUE;
}
/* Retrieve more child nodes if needed here */
/* Find the internal structure entry to update based on the
* key value.
*/
y_if_T_interface *cur_interface = find_interface(name);
if (cur_interface) {
if (LOGDEBUG2) {
log_debug2("\nReplace interface entry %s",
cur_interface->name);
}
/* Replace the existing entry in the internal queue with
* a newly created entry.
*/
dlq_swap(new_interface, cur_interface);
/* clean up the internal structure fields
* and free the structure itself.
*/
free_interface(cur_interface);
} else {
if (LOGDEBUG2) {
log_debug2("\nAdding new interface entry %s",
new_interface->name);
}
/* Add a new internal structure entry into the queue */
add_interface(new_interface);
}
return NO_ERR;
} /* update_interface */
This function exemplifies how to effectively update internal data structures based on user-provided data in EDIT3 callbacks, ensuring synchronization with the updated configuration.
EDIT3 Delete Example
The delete_interface function illustrates the process of removing an interface from an internal data structure within the EDIT3 callback framework.
This function is responsible for the following operations:
Locating Interface to Delete: The function searches the internal data queue to find the matching interface entry to delete.
Removing the Interface: Upon finding the appropriate interface, the function removes it from the internal queue. This step involves detaching the interface entry from the data structure and ensuring that it no longer influences the configuration.
Cleaning Up: Post removal, the function takes care of cleaning up the internal structure fields of the interface and properly freeing up the allocated memory. This step is crucial to prevent memory leaks and maintain the integrity of the internal data structures.
/*
* @brief Delete the interface entry
*
* An example function to illustrated the deletion of the
* internal structure that represents the current interface
* entry
*
* @param name == key leaf value for the entry to delete
* @return status
*/
static status_t
delete_interface (const xmlChar *name)
{
boolean did_delete = FALSE;
/* Find the internal structure entry to delete based on the
* key value.
*/
y_if_T_interface *interface = find_interface(name);
if (interface) {
dlq_remove(interface);
/* clean up the internal structure fields
* and free the structure itself.
*/
free_interface(interface);
did_delete = TRUE;
}
return (did_delete) ? NO_ERR : ERR_NCX_NOT_FOUND;
} /* delete_interface */
The delete_interface function is an essential part of the EDIT3 callbacks, demonstrating the necessary steps to handle the deletion of configuration entries in a clean and efficient manner.
Accessing EDIT3 Control Block
The agt_editcb_t
structure is a comprehensive control block used in
EDIT3 callbacks. It encapsulates all necessary pointers and information required
for the execution of the EDIT3 callback functions.
-
struct agt_editcb_t
EDIT Control Block used to store all the pointers for the EDIT3 callbacks.
Public Members
-
ses_cb_t *scb
Session control block making the request.
-
agt_cfg_transaction_t *txcb
Transaction control block in progress.
-
agt_cbtyp_t cbtyp
Callback type - reason for the callback.
AGT_CB_VALIDATE
AGT_CB_APPLY
AGT_CB_COMMIT
AGT_CB_ROLLBACK
-
op_editop_t editop
Callback editop - the parent edit-config operation type, which is also used for all other callbacks that operate on objects:
OP_EDITOP_LOAD
OP_EDITOP_MERGE
OP_EDITOP_REPLACE
OP_EDITOP_CREATE
OP_EDITOP_DELETE
-
val_value_t *newval
Container object holding the proposed changes to apply to the current config, depending on the editop value.
-
val_value_t *curval
Current container values from the <running> or <candidate> configuration, if any.
Could be NULL for create and other operations.
-
val_value_t *update
Update value contains all new values and the current values from the datastore.
-
const xmlChar *modname
Name of a module the current callback is invoked for.
-
obj_template_t *obj
Object of a a current node in the callback.
-
boolean isvalidate
Transaction is a <validate> operation.
-
boolean in_delete_children_first
This SIL callback is for a nested child and the sil-delete-children-first is active; False if not applicable or the top-node being deleted is the callback.
-
boolean isrunning
TRUE if this Transaction is for the the running datastore.
-
const xmlChar *user_id
user-id backptr from the transaction
-
const xmlChar *client_addr
client address backptr from the transaction
-
const xmlChar *target
datastore target backptr from the transaction
-
const xmlChar *txid_str
transaction ID from the transaction
-
xmlChar *instance_id
instance-identifier path string for target object with all the keys
-
boolean silsa_editcb
Indicate that this is a SIL-SA editcb or not.
-
ses_cb_t *scb
The key elements of this structure are outlined as follows:
- Session Control Block (``scb``): This field contains a pointer to
the session control block that initiated the request, representing the current session's context.
- RPC Message (``msg``): This pointer refers to the incoming RPC message
in progress, detailing the specific request being processed.
- Transaction Control Block (``txcb``): This field points to the transaction
control block in progress, which is crucial for managing the current transaction's state. NOT AVAILABLE IN SIL-SA.
- Callback Type (``cbtyp``): This enumerator indicates the reason for the
callback, which could be one of several types, such as:
AGT_CB_VALIDATE
AGT_CB_APPLY
AGT_CB_COMMIT
AGT_CB_ROLLBACK
- Edit Operation Type (``editop``): This enumerator represents the parent
edit-config operation type, affecting how the callback operates on objects. Possible types include:
OP_EDITOP_LOAD
OP_EDITOP_MERGE
OP_EDITOP_REPLACE
OP_EDITOP_CREATE
OP_EDITOP_DELETE
- New Value (``newval``): This field points to a container/list object that
holds the proposed changes to be applied to the current configuration, varying based on the editop value.
- Current Value (``curval``): This field refers to the current container/list
values from either the <running> or <candidate> configuration, which could be NULL for certain operations like create.
- Update Value (``update``): This field contains a merged value of all new
and current values from the datastore, streamlining the update process.
- Module Name (``modname``): This field specifies the name of the module for
which the current callback is invoked.
- Object Template (``obj``): This field points to the object template of the
current node involved in the callback.
- Is Validate Transaction (``isvalidate``): This boolean indicates whether
the transaction is a <validate> operation.
- Is Running Datastore (``isrunning``): This boolean is TRUE if the
transaction targets the running datastore.
- User ID (``user_id``): This field provides a back-pointer to the user
ID associated with the transaction.
- Client Address (``client_addr``): This field contains a back-pointer to
the client address involved in the transaction.
- Datastore Target (``target``): This field provides a back-pointer to the
targeted datastore of the transaction.
- Transaction ID (``txid_str``): This field contains the transaction ID
as a string, identifying the specific transaction.
- Instance Identifier Path (``instance_id``): This field contains the
Instance Identifier PAth string for the target object including all the keys. Format of the path is a double-quote Xpath.
This control block is pivotal in managing the intricacies of EDIT3 callbacks, ensuring that all necessary data and context are readily available for efficient processing.
SIL-SA EDIT3 Callback
The SIL-SA EDIT3 callback usage is the same as the SIL EDIT3 callback.
The EDIT3 callback provides a more streamlined and efficient approach to handling YANG model-driven configurations. The key feature of EDIT3 in SIL-SA is its utilization of the 'update' value, which combines new user inputs with existing data, simplifying the process of modifying configuration data nodes.
Key Features of EDIT3 in SIL-SA:
Enhanced Data Management: EDIT3 callbacks facilitate more effective management of configuration data by consolidating new and existing values into a single 'update' structure. This approach reduces complexity and potential errors in processing configuration changes.
Efficiency in Processing: The integration of the 'update' value in EDIT3 callbacks minimizes the need for separate handling of new and current values, enabling more efficient processing of configuration changes.
Streamlined Callback Implementation: By eliminating the need for 'child_edit' and reducing redundancy, EDIT3 in SIL-SA provides a cleaner and more concise callback implementation compared to previous callback generations.
Additional Methods to Register EDIT Callbacks
Initial Release: 24.10-2
Availability: SIL and SIL-SA
Starting with version 24.10-2, new registration methods provide enhanced control for registering EDIT2 and EDIT3 callbacks. For example, specific callbacks can be configured during registration to skip the invocation of the Validate and/or Apply phases, enabling more efficient management.
Note
This is NOT an automatically generated API; yangdump-pro will not generate it. Manual updates to the code are required to use this API.
Additional Methods to Register EDIT Callbacks Function
Depending on the 'edit_cbtype' field (type of EDIT callback) specified in the 'extra_flags' field of the agt_cb_extra_flags_t structure, different callback function signatures are used. Refer to the sections below:
For EDIT2 callbacks, see EDIT2 Callback Function.
For EDIT3 callbacks, see EDIT3 Callback Function.
Accessing agt_cb_extra_flags_t structure
The 'agt_cb_extra_flags_t' structure is used to specify extra flags for callback registration. It contains the following fields:
edit_cbtype: Specifies the type of EDIT callback. Possible values include:
AGT_EDIT_CBTYPE_NONE : Not set (default value).
AGT_EDIT_CBTYPE_EDIT : EDIT callback type.
AGT_EDIT_CBTYPE_EDIT2 : EDIT2 callback type.
AGT_EDIT_CBTYPE_EDIT3 : EDIT3 callback type.
skip_validate: A boolean flag that determines whether to skip the Validate Phase. The default value is false.
false: Perform the Validate phase.
true: Skip the Validate phase.
skip_apply: A boolean flag that determines whether to skip the Apply Phase. The default value is false.
false: Perform the Apply phase.
true: Skip the Apply phase.
These fields provide additional control over how callbacks are registered and executed, enabling customization of the callback phases.
Additional Methods to Register EDIT Callbacks Registration and Cleanup
Registration
The registration and cleanup process for this new API is similar to EDIT2 and EDIT3. The callback function is hooked into the server with the 'agt_cb_register_edit_callback_extra' function, as described below. The 'agt_cb_register_edit_callback_extra' function is not automatically generated by yangdump-pro and must be manually implemented. This function registers a single callback function for all applicable phases unless the flags are set to skip specific phases like Apply or Validate. The registration is done during Initialization Phase 1, before the startup configuration is loaded into the running configuration database.
-
status_t agt_cb_register_edit_callback_extra(const xmlChar *modname, const xmlChar *defpath, const xmlChar *version, void *cbfn, const agt_cb_extra_flags_t *extra_flags)
Register an edit callback function with extra flags.
Use the same callback function for all callback phases. All phases will be invoked unless specified to skip in extra_flags. The registration functions are selected based on the provided edit_cbtype in extra_flags.
- 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 for all callback phases
extra_flags -- pointer to extra flags structure specifying options.
- Returns:
status
Register a callback using 'agt_cb_register_edit_callback_extra', similar to other EDIT callbacks but with extra functionality.
Here is an example registration of an EDIT3 Callback with flags to skip both the Validate and Apply phases.
status_t y_name_of_module_init (
const xmlChar *modname,
const xmlChar *revision)
{
status_t res = NO_ERR;
agt_cb_extra_flags_t flags = {
.edit_cbtype = AGT_EDIT_CBTYPE_EDIT3,
.skip_validate = true,
.skip_apply = true
};
res = agt_cb_register_edit_callback_extra(
EXAMPLE_MODNAME,
EXAMPLE_DEFPATH,
EXAMPLE_VERSION,
edit3_callback_example,
&flags);
if (res != NO_ERR) {
return res;
}
return res;
} /* y_name_of_module_init */
Cleanup
The cleanup process uses the same unregister API as EDIT1, EDIT2, and EDIT3 callbacks. Use the 'agt_cb_unregister_callbacks' function to unregister all callbacks for the data node. Refer to the EDIT2/ EDIT3 cleanup documentation for more details.
Database Template
Every NETCONF database has a common template control block, and common set of access functions.
NETCONF databases are not separate entities like separate SQL databases. Each NETCONF database is conceptually the same database, but in different states:
candidate: A complete configuration that may contain changes that have not been applied yet. This is only available if the :candidate capability is advertised by the server. The value tree for this database contains only configuration data nodes.
running: The complete current server configuration. This is available on every NETCONF server. The value tree for this database contains configuration data nodes and non-configuration nodes created and maintained by the server. The server will maintain read-only nodes when <edit-config>, <copy-config>, or :ref"<commit> operations are performed on the running configuration. SIL code should not alter the data nodes within a configuration directly. This work is handled by the server. SIL callback code should only alter its own data structures, if needed.
startup: A complete configuration that will be used upon the next reboot of the device. This is only available if the :startup capability is advertised by the server. The value tree for this database contains only configuration data nodes.
NETCONF also recognized external files via the <url> parameter, if the :url capability is advertised by the server. These databases will be supported in a future release of the server. The NETCONF standard does not require that these external databases support the same set of protocol operations as the standard databases, listed above. A client application can reliably copy from and to an external database, but editing and filtered retrieval may not be supported.
cfg_template_t Structure
The following typedef is used to define a NETCONF database template:
-
struct cfg_template_t
struct representing 1 configuration database
Public Members
-
ncx_cfg_t cfg_id
config ID: Internal configuration ID assigned to this configuration.
-
cfg_location_t cfg_loc
config location: Enumeration identifying the configuration source location.
-
cfg_state_t cfg_state
config state: Current internal configuration state.
-
ncx_transaction_id_t last_txid
last edit transaction ID
-
ncx_transaction_id_t cur_txid
current edit transaction ID
-
time_t last_modified
last modified timestamp
-
xmlChar *name
datastore name string
-
xmlChar *src_url
source URL: URL for use with 'cfg_loc' to identify the configuration source.
-
time_t lock_itime
time_t timestamp for max-lock-hold-time enforcement
-
xmlChar lock_time[TSTAMP_MIN_SIZE]
Date and time string when the configuration was last locked.
-
xmlChar last_ch_time[TSTAMP_MIN_SIZE]
Date and time string when the configuration was last changed.
-
uint32 flags
Internal configuration flags.
Do not use directly.
-
ses_id_t locked_by
Session ID that owns the global configuration lock, if the database is currently locked.
-
boolean locked_by_lockall
Flag indicating the global lock is from lock-all not lock.
-
cfg_source_t lock_src
-
dlq_hdr_t load_errQ
Queue of rpc_err_rec_t structures that represent any <rpc-error> records that were generated when the configuration was loaded, if any.
-
dlq_hdr_t plockQ
-
boolean rwlock_initialized
RWLOCK for this config - used by multiple reader/writer threads according to RWLOCK rules: basically, max one writer, multiple readers, ordered by request time.
-
pthread_rwlock_t rwlock
PTHREADS=1 only: rwlock data.
-
ses_id_t rw_wrlocked_by
PTHREADS=1 only: Single write lock holder, if any.
-
ses_id_t rw_rdlocked_by
Most recent read lock holder only, used for debugging if the background thread locks the cfg for fill_candidate_from_running or other reason then the SID will be zero.
-
boolean rw_wrlocked
PTHREADS=1 only: TRUE if currently write-locked.
-
boolean rw_rdlocked
PTHREADS=1 only: TRUE if currently read-locked.
-
boolean wrlock_pending
TRUE if the wrlock is active and set.
-
val_value_t *root
datastore root value.
btyp == NCX_BT_CONTAINER. The root of the value tree representing the entire database.
-
boolean fake_candidate
TRUE if this is YANG-PATCH request fake candidate template.
-
boolean in_cc_rollback
YPW-2036: flag to allow rollback transaction to indicate if the transaction is due to a confirmed-commit timeout Only used in agt.
Set in agt_ncx_cancel_confirmed_commit
-
boolean defer_load
TRUE if load is deferred due to sil-sa bundles.
-
ncx_cfg_t cfg_id
cfg_template_t Access Functions
The file ncx/cfg.h
contains some high-level database access
functions that may be of interest to SIL callback functions for custom
RPC operations. All database access details are handled by the server if
the database edit callback functions are used (associated with a
particular object node supported by the server). The following table
highlights the most commonly used functions. Refer to the H file for a
complete definition of each API function.
cfg_new_template |
Create a new configuration database. |
cfg_free_template |
Free a configuration database. |
cfg_get_state |
Get the current internal database state. |
cfg_get_config |
Get a configuration database template pointer, from a configuration name string. |
cfg_get_config_name |
Get the config name from its ID. |
cfg_get_config_id |
Get a configuration database template pointer, from a configuration ID. |
cfg_set_target |
Set the CFG_FL_TARGET flag in the specified config. |
cfg_fill_candidate_from_running |
Fill the <candidate> config with the config contents of the <running> config. |
cfg_fill_candidate_from_startup |
Fill the <candidate> config with the config contents of the <startup> config. |
cfg_fill_candidate_from_inline |
Fill the candidate database from an internal value tree data structure. |
cfg_get_dirty_flag |
Returns TRUE if the database has changes in it that have not been saved yet. This applies to the candidate and running databases at this time. |
cfg_clear_dirty_flag |
Clear the cfg dirty flag upon request. |
cfg_clear_running_dirty_flag |
Clear the running dirty flag when it is saved to NV-storage or loaded into running from startup. |
cfg_clear_candidate_dirty_flag |
Clear the candidate dirty flag when it is saved to NV-storage or loaded into running from startup. |
cfg_get_dirty_flag |
Get the config dirty flag value. |
cfg_ok_to_lock |
Check if the database could be successfully locked by a specific session. |
cfg_ok_to_unlock |
Check if the database could be successfully unlocked by a specific session. |
cfg_ok_to_read |
Check if the database is in a state where read operations are allowed. |
cfg_ok_to_write |
Check if the database could be successfully written by a specific session. Checks the global configuration lock, if any is set. |
cfg_is_global_locked |
Returns TRUE if the database is locked right now with a global lock. |
cfg_get_global_lock_info |
Get some information about the current global lock on the database. |
cfg_is_partial_locked |
Check if the specified config has any active partial locks. |
cfg_add_partial_lock |
Add a partial lock the specified config. This will not really have an effect unless the CFG_FL_TARGET flag in the specified config is also set . For global lock only. |
cfg_find_partial_lock |
Find a partial lock in the specified config. |
cfg_first_partial_lock |
Get the first partial lock in the specified config. |
cfg_next_partial_lock |
Get the next partial lock in the specified config. |
cfg_delete_partial_lock |
Remove a partial lock from the specified config. |
cfg_ok_to_partial_lock |
Check if the specified config can be locked right now for partial lock only. |
cfg_get_root |
Get the config root for the specified config. |
cfg_lock |
Get a global lock on the database. |
cfg_unlock |
Release the global lock on the database. |
cfg_unlock_ex |
Release the global lock on the database. Do not always force a remove changes. This is needed for the <unload> operation which locks the datastores while deleting data nodes and schema nodes for the module being unloaded. |
cfg_release_locks |
Release all locks on all databases. |
cfg_release_partial_locks |
Release any configuration locks held by the specified session. |
cfg_get_lock_list |
Get a list of all the locks held by a session. |
cfg_update_last_ch_time |
Update the last-modified time-stamp. |
cfg_update_last_txid |
Update the last good transaction ID. |
cfg_update_stamps |
Update the last-modified and last-txid stamps. |
cfg_get_last_ch_time |
Get the last-modified time-stamp. |
cfg_get_last_txid |
Get the last good transaction ID. |
cfg_sprintf_etag |
Write the Entity Tag for the datastore to the specified buffer. |
cfg_get_startup_filespec |
Get the filespec string for the XML file to save the running database. |
Database Access Utilities
Finding Data Nodes with XPath
The internal XPath API can be used to access configuration data nodes. The data should not be altered. This API should be considered read-only.
Step 1) Create the XPath parser control block
-
xpath_pcb_t *xpath_new_pcb(const xmlChar *xpathstr, xpath_getvar_fn_t getvar_fn)
malloc a new XPath parser control block
xpathstr is allowed to be NULL, otherwise a strdup will be made and exprstr will be set
Create and initialize an XPath parser control block
- Parameters:
xpathstr --
XPath expression string to save (a copy will be made)
NULL if this step should be skipped
getvar_fn --
callback function to retirieve an XPath variable binding
NULL if no variables are used
- Returns:
pointer to malloced struct, NULL if malloc error
xpath_pcb_t *pcb =
xpath_new_pcb((const xmlChar *)"/interfaces/interface", NULL);
if (pcb == NULL) {
return ERR_INTERNAL_MEM;
}
Step 2) Evaluate the XPath expression
-
xpath_result_t *xpath1_eval_expr(xpath_pcb_t *pcb, val_value_t *val, val_value_t *docroot, boolean logerrors, boolean configonly, status_t *res)
Evaluate an XPath expression use if the prefixes are YANG: must/when.
Evaluate the expression and get the expression nodeset result
- Parameters:
pcb -- XPath parser control block to use
val -- start context node for value of current()
docroot -- ptr to cfg->root or top of rpc/rpc-replay/notif tree
logerrors --
TRUE if log_error and ncx_print_errormsg should be used to log XPath errors and warnings
FALSE if internal error info should be recorded in the
xpath_result_t struct insteadconfigonly -- config mode
res -- [out] address of return status
*res is set to the return status
- Returns:
malloced result struct with expr result NULL if no result produced (see *res for reason)
'docroot' is usually a datastore root like 'cfg->cfg_root'
context node can be an interior node, but can also be the 'docroot'
xpath_result_t *result =
xpath1_eval_expr(pcb,
root,
root,
FALSE, // logerrors
TRUE, // configonly
&res);
Step 3) Iterate through the node-set result
-
xpath_resnode_t *xpath_get_first_resnode(xpath_result_t *result)
Get the first result in the renodeQ from a result struct.
- Parameters:
result -- result struct to check
- Returns:
pointer to resnode or NULL if some error
-
xpath_resnode_t *xpath_get_next_resnode(xpath_resnode_t *resnode)
Get the next result in the renodeQ from a result struct.
- Parameters:
resnode -- current result node to get next from
- Returns:
pointer to next resnode or NULL if some error
-
val_value_t *xpath_first_resnode_valptr(xpath_resnode_t *resnode)
Get the first result in the renodeQ from a result struct.
- Parameters:
resnode -- result node struct to check
- Returns:
pointer to value node resnode or NULL if some error
-
val_value_t *xpath_next_resnode_valptr(xpath_resnode_t *resnode, val_value_t *valptr)
Get the next node val pointer from a result node struct.
- Parameters:
resnode -- pointer of result node struct
valptr -- current value pointer to get next for
- Returns:
the next val pointer or NULL if some error
Note that XPath result nodes may reference a value node header, so it is safest to iterate through the value instances in the resnode. If there is only one entry this code will still work:
xpath_resnode_t *resnode = xpath_get_first_resnode(result);
for (; resnode; resnode = xpath_get_next_resnode(resnode)) {
val_value_t *valnode = xpath_first_resnode_valptr(resnode);
for (; valnode; valnode = xpath_next_resnode_valptr(resnode, valnode)) {
// do something with valnode.... e.g dump the value if debug2
if (LOGDEBUG2) {
log_debug2("\nGot resnode value:\n");
val_dump_value(valnode, 0, DBG2);
}
}
}
Determining if a Value Node is Set
A val_value_t structure is usually allocated with val_new_value() and then initialized with val_init_from_template().
In order to determine if the value has been set by a client, and not set by default or simply initialized, use the API function 'val_is_value_set'.
-
boolean val_is_value_set(val_value_t *val)
Check if a value has been set by a client It has to be initialized and not set by default to return true.
- Parameters:
val -- value to check
- Returns:
true if value has been set
false if has not been set, or set to default, or the code cannot tell if the value is more than initialized
Determining if the SIL Callback is the Deepest for the Edit
A datastore edit includes data for a subtree of configuration data. It is possible that multiple SIL callbacks will be invoked for the edit operation.
The function 'agt_val_silcall_is_deepest' from file
agt/agt_val_silcall.h
can be used to determine if the current
callback is the deepest SIL invocation for the edit.
-
boolean agt_val_silcall_is_deepest(rpc_msg_t *msg)
Check if the current nested SIL callback is at the deepest level for the current edit.
Example
In the example above, the code is generated as EDIT2 code so there are no callbacks for leafs
This function would return TRUE for list 'tlist3' and FALSE for all ancestor node callbacks (tnest, tlist, list2)
FALSE if the current SIL callback is not at the deepest level for the edit that contains the silcall record. There is at least one nested_silcall involved in the current edit that is at a child level compared to the current node being called
FALSE if there is no current silcall set
- Parameters:
msg -- RPC message to check
- Returns:
TRUE if the current SIL callback is at the deepest level for the edit that contains the silcall record