Operational Data
Operational data is data that is set as “config false” in the YANG file. Operational state and statistics are common forms of operational data. These nodes are supported within SIL code in three ways:
object template based “GET2” callback
value node based static operational data
value node based "GET1" virtual operational data
GET1 Callback
Note
GET1 Callbacks are obsolete starting in 24.10-1
Legacy SIL code can continue using GET1 callbacks
No new YANG instrumentation using GET1 callbacks will be supported.
GET1 code generation in yangdump-pro has not been removed but is no longer supported.
In almost all cases it is better to use GET2 instead of GET1 callbacks!
This section describes how to manage non-configurable or just operational data, what type of callbacks are available for non-configurable nodes and how to utilize these type of callbacks.
Operational data nodes are supported within SIL code in three ways:
value node based virtual data (GET1 callback)
value node based static data (GET1 callback)
object template based GET2 callback
GET1 Callback Function
The following function template definition is used for GET1 callback functions:
-
typedef status_t (*getcb_fn_t)(ses_cb_t *scb, getcb_mode_t cbmode, const val_value_t *virval, val_value_t *dstval)
GET1 Callback function for agent node get handler.
- Param scb:
session that issued the get (may be NULL) can be used for access control purposes
- Param cbmode:
reason for the callback
- Param virval:
place-holder node in the data model for this virtual value node
- Param dstval:
[out]
pointer to value output struct
*dstval should be filled in, depending on the callback reason
- Return:
status
The 'scb' pointer represents a session control block structure
that is defined in the ncx/ses.h
. This control block is
primarily used for error reporting.
The 'cbmode' enumeration identifies the callback mode for the GET request,
that is defined in the ncx/getcb.h
.
This control block specifies what retrieval
type has this GET callback. GET1 callback should have GETCB_GET_VALUE
type, if the type is different, the error should be reported, as
specified in the examples later in this section.
The 'virval' pointer represents a data node as a place-holder node in the data model for this virtual value node. The GET1 callback function pointer is stored in this virtual value.
The 'dstval' pointer represents the data node that is being filled in. The GET1 callback is expected to fill in this value node and set the value.
Create a Virtual Node to Use GET1
The GET1 callback design is only supported for SIL code (running in the netconfd-pro process). A virtual data node has to be installed within the <running> datastore for every instance required. The GET1 callback is only supported for operational data. The server expects all configuration data to be a plain val_value_t node without a GET1 callback.
A GET1 callback is required for each "config false" leaf. In the example YANG module, the "observed-speed" leaf needs a GET1 callback.
GET1 Leaf Initialization
-
val_value_t *agt_make_virtual_leaf(obj_template_t *parentobj, const xmlChar *leafname, getcb_fn_t callbackfn, status_t *res)
make a val_value_t struct for a specified virtual leaf or leaf-list
This is a SIL only API for creating a GET1 callback.
Use GET2 callbacks instead! GET1 is deprecated!
!!! This function works on all value node types !!! !!! Check for leaf or leaf-list removed !!!
- Parameters:
parentobj -- parent object to find child leaf object
leafname -- name of leaf to find (namespace hardwired)
callbackfn -- get callback function to install
res -- [out] address of return status; *res return status
- Returns:
malloced value struct or NULL if some error
/********************************************************************
* FUNCTION create_readonly_leaf
*
* Make read-only child nodes
* Path: /interfaces/interface/observed-speed
*
* INPUTS:
* parentval == the parent struct to use for new child nodes
*
* RETURNS:
* error status
********************************************************************/
static status_t
create_readonly_leaf (val_value_t *parentval)
{
status_t res = NO_ERR;
/* add /interfaces/interface/observed-speed */
val_value_t *childval =
agt_make_virtual_leaf(VAL_OBJ(parentval),
(const xmlChar *)"observed-speed",
observed_speed_get,
&res);
if (childval != NULL) {
res = val_child_add(childval, parentval);
if (res != NO_ERR) {
val_free_value(childval);
}
}
return res;
} /* create_readonly_leaf */
GET1 Callback Example
The following example shows the GET1 callback that will be called when the value for the "observed-speed" leaf is needed.
GET1 Callback Example
-
status_t val_set_simval_obj(val_value_t *val, obj_template_t *obj, const xmlChar *valstr)
Set an initialized val_value_t as a simple type.
Set a pre-initialized val_value_t as a simple type from an object template instead of individual fields Calls val_set_simval with the object settings
This API should be used by GET1 callbacks where 'retval' has been allocated but not initialized yet
- Parameters:
val -- value struct to set
obj -- object template to use
valstr -- simple value encoded as a string to set
- Returns:
status
/********************************************************************
* FUNCTION observed_speed_get
*
* Callback function for agent node get handler
*
* Fill in 'dstval' contents
*
********************************************************************/
static status_t
observed_speed_get (ses_cb_t *scb,
getcb_mode_t cbmode,
const val_value_t *virval,
val_value_t *dstval)
{
status_t res = NO_ERR;
/* remove the next line if scb is used */
(void)scb;
/* remove the next line if virval is used */
(void)virval;
if (cbmode != GETCB_GET_VALUE) {
return ERR_NCX_OPERATION_NOT_SUPPORTED;
}
/* set the speed var here */
const xmlChar *speed = (const xmlChar *)"1000";
res = val_set_simval_obj(dstval, dstval->obj, speed);
return res;
} /* observed_speed_get */
GET1 Retrieval Example
The client might send the following <get> request:
<?xml version="1.0" encoding="UTF-8"?>
<rpc message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<get>
<filter type="subtree">
<interfaces xmlns="http://yumaworks.com/ns/interfaces">
<interface>
<name>supported</name>
</interface>
</interfaces>
</filter>
</get>
</rpc>
The server may reply with:
<rpc-reply message-id="101"
xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<interfaces xmlns="http://yumaworks.com/ns/interfaces">
<interface>
<name>supported</name>
<observed-speed>1000</observed-speed>
</interface>
</interfaces>
</rpc-reply>
GET2 Callback
A GET2 callback function is usually mapped to a single data node. The server will issue “get” and “getnext” requests via this API, and the callback code will return value nodes for the data it is managing. These data nodes are not maintained in the server Data Tree. They are not cached in the server.
This API uses 1 callback per object template. The server will issue “get” and “getnext” requests via the API, and the callback code will return value nodes for the data is is managing. These data nodes are not maintained in the server data tree. They are not cached in the server.
Local SIL and remote SIL-SA callbacks are supported for operational data. These callbacks are expected to return some or all of the terminal child nodes (leaf, leaf-list, anyxml) when invoked.
GET2 callbacks for "conventional" Configuration data are not supported at this time. If NMDA is used, then GET2 callbacks for configuration data is supported for the <operational> datastore.
For the Global version of this GET2 callback, see Global GET2 Callback.
Which Nodes Have GET2 Callbacks
Complex child nodes are expected to have their own GET2 callbacks registered.
The following complex node types are expected to have GET2 callbacks registered:
choice: expected to return the name of the active case and terminal nodes from the active case
container: expected to return child terminal nodes
list: expected to return list keys and maybe other terminal nodes
Callbacks for terminal nodes are allowed, but only if their parent is a config=true node, that includes top level terminal nodes.
The following terminal node types are expected to have GET2 callbacks registered in this case:
leaf: expected to return the leaf value
leaf-list: expected to return all values for the leaf-list
anydata: expected to return the entire anydata value tree
anyxml: expected to return the entire anyxml value tree
The server will expect configuration data for parent nodes to be present in the target datastore.
If the retrieval request includes config=false nodes, then the server will check each child terminal node that is config=false for a GET2 callback function.
If it exists, then it will be used for retrieval of that data node.
If not, the current config=true node will be checked for child nodes (static or dynamic operational data).
GET2 Callback Function
The following function template definition is used for GET2 callback functions:
-
typedef status_t (*getcb_fn2_t)(ses_cb_t *scb, xml_msg_hdr_t *msg, getcb_get2_t *get2cb)
GET2 PRODUCER FUNCTION.
Callback function for server object handler get2 callback Used to provide main and/or subsystem retrieval of instances of a specific named object
- Param scb:
session control block making the request
- Param msg:
incoming XML message header
- Param get2cb:
[inout] get2 control block for this callback request
return_keyQ is full of any new keys added for this entry — only if 'obj' is a list
return_valQ is filled with malloced val_value_t nodes
If object tested is a choice the a backptr to a constant string containing the case name that is active
- Return:
status NO_ERR if executed OK and found OK
ERR_NCX_NO_INSTANCE warning if no instance found
GET2 Control Block
The 'getcb_get2_t' structure contains all the input, output, and state data used for GET2 transactions.
-
struct getcb_get2_t
GET2 control block.
Public Members
-
dlq_hdr_t qhdr
queue header
-
xmlChar *txid_str
transaction ID string
-
obj_template_t *obj
object template containing this callback
-
val_nodetest_fn_t testfn
value node test function (may be obsolete for get2)
-
dlq_hdr_t keyQ
Q of malloced val_value_t; 1 entry for each key leaf includes the ancestor keys and keys for the current list (if applicable) If the set of keys for the current list is incomplete, then the provided keys are 'fixed'.
-
dlq_hdr_t matchQ
Q of malloced val_value_t; 1 entry for each content-match leaf in the subtree or XPath filter; these leafs only apply to the current object.
-
dlq_hdr_t selectQ
Q of malloced getcb_get2_select_t; 1 entry for each child select node of the object in this get2cb these select nodes only apply to the current object if the 'select_only' flag is set then this queue is used.
An empty selectQ means only keys should be returned (for a list) For a choice the active case must be set. For a P-container, the callback has to return NO_ERR instead of ERR_NCX_NO_INSTANCE
The get2 callback can ignore the select_only flag and selectQ The extra returned values will be ignored by the caller. Q of getcb_get2_select_t
-
getcb_mode_t cbmode
reason for the callback (get or getnext)
-
uint32 max_entries
max instances to get 0 for all entries, 1 .
. N for specific max
-
uint32 max_levels
0 for all levels, 1 .
. N for max max_levels forced to 1 if testmode=TRUE
-
boolean oper_data
TRUE to get operational data.
-
boolean config_data
TBD: TRUE to get configuration data not supported yet! All config must be in the cfg->root val_value_t tree.
-
boolean expand_varexpr
variable expressions: TRUE if a varexpr node should be expanded; FALSE if printed as an expression
-
boolean keys_only
keys-only: TRUE if only the key leafs are desired from list objects; FALSE if normal retrieval
-
boolean select_only
select: TRUE if only the selectQ child nodes are desired from the parent for this callback; FALSE if no select mode retrieval
-
boolean with_defaults
with_defaults: TRUE if default nodes should be returned this is needed for operational data because the server does not process when-stmts on operational data, so the instrumentation has to return operational defaults if they are requested
-
getcb_api_mode_t api_mode
api_mode: consumer API callback mode
-
boolean with_origin
get-request with-origin flag
-
val_value_t *start_add_key
first key val node that was moved from the return_keyQ to the keyQ for nested nodes to process; needs to be removed after each nested list is processed
-
boolean acmtest_result
acmtest result used for NACM check
-
boolean last_sibling
flag to track JSON state
-
boolean isfirst_nokey
flag to track JSON state
-
boolean first_llsibling
leaf-list siblings
-
boolean last_llsibling
leaf-list siblings
-
boolean islast
Used only for AIO RESTCONF processing.
also used for JSON subtree proc
-
boolean isfirst
also used for JSON subtree proc
-
boolean aio_done
AIO processing is completed.
-
boolean first_child
Used for JSON subtree proccessing.
first child done
-
boolean first_sibling
first sibling done
-
boolean force_lastsibling
force JSON state
-
boolean force_lastsib_value
force JSON state
-
boolean force_array_obj
force JSON array output
-
boolean finish_list
Used for CBOR processing to know if the special finish-list callback mode is needed.
-
boolean more_data
set by the callback function if there are more instances that follow the specified instance; this is a global flag that applies to the entire response
-
dlq_hdr_t getbulkQ
set by the callback function if this is a list callback and multiple entries are returned; this is a global queue that applies to the entire response
Q of getcb_get2_getbulk_t
-
xmlChar *active_case_modname
set by a choice test_mode callback to return the name of the active case; If the active_case_modname is NULL then the module name of the parent choice-stmt will be used
-
xmlChar *active_case
name of the active case
-
dlq_hdr_t return_keyQ
Q of malloced val_value_t.
-
dlq_hdr_t return_valQ
Q of malloced val_value_t.
-
val_value_t return_val
if just 1 instance is returned, then the return_val will be used; if the btyp is set to something other than NCX_BT_NONE, then the server will use this value; otherwise 1 or more malloced val_value_t structs are expected in the return_valQ
THIS STRUCT MUST NOT BE USED IN GETBULK MODE
-
dlq_hdr_t responseQ
if this is a request that causes multiple responses then the responseQ will have each response get2cb
-
boolean match_test_done
content-match done flag ignored unless the matchQ is non-empty If TRUE.
indicates that the callback performed the requested content-match tests and the returned output is post-filter If FALSE, then the content-match tests were not done
-
boolean wrote_some_lists
If TRUE then Last list failed BUT some of the entries were written successfully.
So need to write comma after this list object.
-
val_value_t *parent_val
save parent backtrs for when-stmt processing of GET2 data
-
struct getcb_get2_t_ *parent_cb
backptr to parent CB
-
dlq_hdr_t return_aioQ
Q of malloced val_value_t.
Used for sil-aio-get2 extension only. Callbacks will fill in data into this Queue. The callback will be called only once and the server will expect the return_aioQ to be filled in with the whole set of children.
-
ncx_nmda_ds_t nmda_ds
save NMDA datastore for GET operational
-
ncx_nmda_origin_t nmda_origin
caller will set the return nmda_origin
-
ncx_msg_encoding_t aio_encoding
In AIO GET2 callback is used with JSON/XML buffers the encoding will represent corresponding encoding type.
-
xmlChar *aio_return_buff
AIO XML or JSON malloced buffer from callback to use.
Must be freed after it is converted to val value.
-
ncx_sm_mpid_t *sm_mpid
If schema-mount is in use then the MPID is set for GET2 callbacks that have a ancestor that is a rootcb (MPID) This is a backpointer NOT a malloced struct.
-
boolean sm_mpid_malloced
The MPID will be malloced on the SIL-SA side (TRUE) otherwise set to FALSE if the sm_mpid is a backptr.
-
void *user_data_ref
User Data Reference Can be used by GET2 callbacks to store and reference a pointer during the GET2 callback walk through lists and data structures.
Initially set to NULL for the first get2cb created during a walk
Up to the GET2 callback to set this field using the GETCB_GET2_USER_DATA_REF() macro
The server will copy the value from the parent get2cb when creating a child get2cb during a tree traversal
The pointer must not be malloced and stored only here. There is no way to cleanup this pointer and the GET2 walk may exit on error at any time
-
uint32 user_data_index
User Data Index Can be used by GET2 callbacks to store and reference an index during the GET2 callback walk through lists and data structures.
Many code scrubbers do not like casting pointers as numbers
Initially set to 0 for the first get2cb created during a walk
Up to the GET2 callback to set this field using the GETCB_GET2_USER_DATA_INDEX() macro
The server will copy the value from the parent get2cb when creating a child get2cb during a tree traversal
-
boolean pre_process
YPW-2273: pre_process_mode is TRUE if Subtree filter is doing Pre process Selection nodes to see if the server should start to write the parent node or not.
-
dlq_hdr_t qhdr
This data structure contains the fields used by the server and the callback to exchange data.
getcb Request Fields:
set by 'getcb' before the GET2 callback is invoked
txid_str: GET2 transaction IF string
GETCB_GET2_TXID_STR(cb) access macro
obj: pointer to obj_template_t for the object being retrieved
GETCB_GET2_OBJ(cb) access macro
testfn: test filter function for checking nodes before processing (may be NULL)
GETCB_GET2_TESTFN access macro
keyQ: queue of val_value_t structs representing the key leafs for the request. These are keys for the ancestor-or-self lists for this object.
Fixed keys are identified using the VAL_IS_FIXED_VALUE(keyval) macro
The GET2 callback must only return entries matching the provided key values
If no value is provided for a key then the first value is requested
GETCB_GET2_KEYQ(cb) access macro
GETCB_GET2_FIRST_KEY(cb) macro to return the first val_value_t in the queue
GETCB_GET2_NEXT_KEY(cb, cur) macro to return the next val_value_t in the queue
matchQ: queue of val_value_t structs representing non-key leaf content-match nodes
These are always simple child nodes of the object identified by 'obj'
The content-match test is optional to implement by the GET2 callback
If the content-match tests have been performed by the GET2 callback, then the return flag is set with the GETCB_GET2_CONTENT_MATCH_DONE() macro
All entries in the matchQ must match the entry being returned. If not, then an ERR_NCX_NO_INSTANCE status is returned instead
GETCB_GET2_MATCHQ(cb) access macro
GETCB_GET2_FIRST_MATCH(cb) macro to return the first val_value_t in the queue
GETCB_GET2_NEXT_MATCH(cb, cur) macro to return the next val_value_t in the queue
cbmode: the GET2 callback mode
GETCB_GET_VALUE
for get-exactGETCB_GETNEXT_VALUE
for get-nextGETCB_GET2_CBMODE(cb) access macro
max_entries: maximum number of values to return (for getnext mode)
0 == return all entries
1 – N == return at most this number of entries
Currently set to 1 by the server
GETCB_GET2_MAX_ENTRIES(cb) access macro
max_levels: maximum depth of subtrees parameter to determine if the maximum level has been reached
GETCB_GET2_MAX_LEVELS(cb) access macro
oper_data: true if config=false data should be returned
GETCB_GET2_OPER_DATA(cb) access macro
config_data: true if config=true data should be returned
GETCB_GET2_CONFIG_DATA(cb) access macro
pre-process: true if Subtree filter is doing Pre process Selection nodes to see if the server should start to write the parent node or not.
GETCB_GET2_PRE_PROCESS(cb) access macro
expand_varexpr: true is variable expressions should be expanded; false to print the variable instead
GETCB_GET2_EXPAND_VAREXPR(cb) access macro
keys_only: true if only list keys should be returned; false for all terminal nodes
GETCB_GET2_KEYS_ONLY(cb) access macro
with_defaults: true if default nodes should be returned; false if they are not requested
GETCB_GET2_WITH_DEFAULTS(cb) access macro
api_mode: the consumer walker callback API mode
GETCB_API_MODE_NORMAL
: entries walked with BEGIN, TERM, ENDGETCB_API_MODE_1SHOT
: entry called with 1SHOT, no child nodes walkedGETCB_GET2_API_MODE(cb) access macro
getcb Response Fields:
Set by the GET2 callback before it returns
more_data: set to true if there are more instances of this object to retrieve; false otherwise
GETCB_GET2_MORE_DATA(cb) access macro
match_test_done: set to true if the GET2 callback performed the request content match test(s); false if not or no match tests requested
GETCB_GET2_MATCH_TEST_DONE(cb) access macro
active_case_modname: name of the module for the module namespace of the active case; set to NULL for the same module name as the choice
Ignored unless the 'obj' type is 'choice'
GETCB_GET2_ACTIVE_CASE_MODNAME(cb) access macro
active_case: name of the active case
Ignored unless the 'obj' type is 'choice'
GETCB_GET2_ACTIVE_CASE(cb) access macro
return_keyQ: queue of val_value_t structs representing the key leafs of the returned value
Values from the keyQ can be transferred to this queue
GETCB_GET2_RETURN_KEYQ(cb) access macro
GETCB_GET2_FIRST_RETURN_KEY(cb) macro to return the first val_value_t in the queue
GETCB_GET2_NEXT_RETURN_KEY(cb, cur) macro to return the next val_value_t in the queue
return_valQ: queue of val_value_t structs representing the non-key terminal nodes of the returned value
Only the first value is used at this time, except for leaf-list nodes
Should be empty if the 'keys-only' flag is set in the request
GETCB_GET2_RETURN_VALQ(cb) access macro
GETCB_GET2_FIRST_RETURN_VAL(cb) macro to return the first val_value_t in the queue
GETCB_GET2_NEXT_RETURN_VAL(cb, cur) macro to return the next val_value_t in the queue
return_val: inline val_value_t to return a single leaf
Can be used instead of the return_valQ to avoid mallocing a val_value_t
GETCB_GET2_RETURN_VAL(cb) access macro
GETCB_GET2_RETURN_VAL_SET(cb) macro to test if the value is set
return_aioQ: queue of val_value_t structure representing the All In One (AIO) nodes of the returned value
GETCB_GET2_RETURN_AIOQ(cb) access macro
GETCB_GET2_FIRST_RETURN_AIO_VAL(cb) macro to return the first val_value_t in the queue
GETCB_GET2_NEXT_RETURN_AIO_VAL(cur) macro to return the next val_value_t in the queue
aio_return_buff: AIO XML or JSON malloced buffer from callback to use instead of val_value_t structures
GETCB_AIO_BUFFER(cb) access macro
GETCB_AIO_ENCODING(cb) macro to access AIO callback encoding
getcb User State Fields:
There are two new user fields that are available starting in 21.10-6. Refer to the GET2 Control Block User Data section for details.
user_data_ref: user state data pointer
void * pointer
Access with macro GETCB_GET2_USER_DATA_REF
user_data_index: user state data index
uint32
Access with macro GETCB_GET2_USER_DATA_INDEX
GET2 Function Return Data
Each GET2 callback returns a status code:
NO_ERR
: data is returnedERR_NCX_NO_INSTANCE
: the specified data instance does not existOther status code: some error occurred
The callback is expected to add data to the 'get2cb' via API functions, depending on the object type:
Object |
Data Returned |
---|---|
container |
Returns the child terminal nodes in the container |
list |
Returns the keys and perhaps the rest of the child terminal nodes. Set “more_data” to trigger “getnext” until no more instances found |
choice |
Returns the name of the active case and child terminal nodes in the active case |
anyxml |
Returns the entire anyxml value tree |
anydata |
Returns the entire anydata value tree |
leaf |
Returns the leaf value |
leaf-list |
Returns all values for the leaf-list |
case |
This node type does not have a callback |
GET2 Processing Procedure
The server will send a <get-request> server request message to each subsystem that has registered a get2 callback for the specific object.
For single-instance nodes (container, leaf, choice, anyxml) only a “get” request will be made.
For a leaf-list node, only “get” requests will be made, but all instances of the leaf-list are returned at once (max-entries parameter equals zero).
For a list node, a “get” request will be made if the keys are known.
A “get” request that contains no keys will cause the first instance to be returned.
Entries are ordered by their key values, exactly as specified in the YANG list. If there is no key-stmt defined, then the callback will still return the first entry, except the server will not attempt any sorting if multiple entries are returned (E.g 1 from each subsystem).
It is always up to the callback to know what is the “best” next entry, given the keys that are returned.
The callback examined the keys (if any) and fills in the return_keys.
If the “keys-only” flag is set, then no other terminal nodes are returned.
If there are more list entries, then the “more-data” flag is set in the get-response.
The server will issue “getnext” requests as needed until the “more-data” flag is not present in the response.
The “max-entries” field will be set to 1. It may be set higher in a future release to support getbulk retrieval.
GET2 Callback Initialization and Cleanup
The SIL or SIL-SA phase 1 initialization code is used to register the GET1 callback functions used in the server. The GET1 callback is unregistered during the SIL or SIL-SA cleanup phase.
-
status_t agt_cb_register_get_callback(const xmlChar *modname, const xmlChar *defpath, const xmlChar *version, getcb_fn2_t get_cbfn)
Register an object specific GET 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
get_cbfn -- address of callback function to use for GET callbacks
- Returns:
status
Example init1 function:
static status_t init_interfaces (void)
{
// ...
status_t res =
agt_cb_register_get_callback(EXAMPLE_MODNAME,
(const xmlChar *)"/interfaces-state",
EXAMPLE_VERSION,
get2_container_interfaces);
if (res != NO_ERR) {
return res;
}
// ...
} /* init_interfaces */
-
void agt_cb_unregister_callbacks(const xmlChar *modname, const xmlChar *defpath)
Unregister all EDIT callback functions for a specific object.
- Parameters:
modname -- module containing the object for this callback
defpath -- definition XPath location
Example cleanup function:
void interfaces_cleanup (void)
{
// ...
agt_cb_unregister_callbacks(EXAMPLE_MODNAME, (const xmlChar *)"/interfaces-state");
//...
}
GET2 Control Block User Data
It is possible for a SIL or SIL-SA GET2 callback to maintain a user data pointer and/or a user index across GET2 calls from the server.
The GET2 callback can use these fields to store implementation-specific state data to make the GET2 lookup more efficient.
The user data includes a "void *" field and a "uint32" field.
The server will maintain the user data values if the are changed in the GET2 control block.
These fields are not used by the server at all.
There are no cleanup APIs so this data must not be malloced
GET2 Macros
The macros defined in 'getcb.h' should be used to access the user data fields.
// access to void * field
#define GETCB_GET2_USER_DATA_REF(G) (G)->user_data_ref
// access the uint32 field
#define GETCB_GET2_USER_DATA_INDEX(G) (G)->user_data_index
SIL Code Support
The server has full awareness of the parent and child traversals within the main server process.
The server will copy the user state data from parent control block to child control block when traversing a subtree with GET2 callbacks.
The same control block is available for 'getnext' iterations for all GET2 callbacks.
The SIL code uses the GET2 Macros to get and set the user data.
SIL-SA Code Usage
Support for SIL-SA usage is available starting in the 23.10-4 release.
The 'sil-sa-app' code does not have any awareness of the GET2 tree traversal that is in progress. The SIL-SA GET2 callback code should not rely on the server automatically clearing the user data fields during tree traversal.
The 'sil-sa-app' will maintain the user data fields for the entire subsystem. The SIL-SA init code (or other code) can access these subsystem fields, using speciual functions.
For example, if the SIL-SA code has GET2 callbacs for
/top-foo
and/top-bar
, then the user data fields will not be automatically cleared after the first tree is traversed, and before the second tree.
SIL-SA Extra Functions
The following functions can be used to access the static data maintained by the subsystem:
-
void sil_sa_get_user_data_parms(void **data_ref, uint32 *data_index)
Get the current GET2_USER_REF saved values.
User data tracking for GET2 support for SIL-SA Macros:
GETCB_GET2_USER_DATA_REF
GETCB_GET2_USER_DATA_INDEX
These macros can be set and retrieved outside of a GET2 callback. Each subsystem maintains saved values for each of these variables to use as the initial value to use for GET2 callbacks.
A GET2 callback may change the values during the callback.
- Parameters:
data_ref -- [out] return the current USER_DATA_REF value
data_index -- [out] return the current USER_DATA_INDEX value
-
void sil_sa_set_user_data_parms(void *data_ref, uint32 data_index)
Set the current GET2_USER_REF saved values.
User data tracking for GET2 support for SIL-SA Macros:
GETCB_GET2_USER_DATA_REF
GETCB_GET2_USER_DATA_INDEX
These macros can be set and retrieved outside of a GET2 callback. Each subsystem maintains saved values for each of these variables to use as the initial value to use for GET2 callbacks.
A GET2 callback may change the values during the callback.
- Parameters:
data_ref -- set the current USER_DATA_REF value
use the value 'NULL' to clear this data
data_index -- set the current USER_DATA_INDEX value
Use the value '0' to clear this data
SIL-SA Example
The following YANG module is used for this example:
module m3 {
namespace "urn:vendor:yumaworks:m3";
prefix m3;
revision 2023-10-20;
container m3-data {
config false;
list m3-list {
key a;
leaf a { type uint8; }
leaf b { type uint32; }
container m3-con {
leaf c { type uint32; }
}
}
}
}
The SIL-SA code is generated with defaults:
> make_sil_sa_dir m3
The file 'src/u_m3.c' is changed as follows:
Add a static variable for the index
/* put your static variables here */
static uint32 user_data_index = 0;
Initialize the data in the 'u_m3_init' function
/* put your module initialization code here */
user_data_index = 0;
Update the logging code in the GET2 callbacks (optional)
Function 'u_m3_m3_con_get':
status_t u_m3_m3_con_get (
getcb_get2_t *get2cb,
uint8 k_m3_a)
{
if (LOGDEBUG) {
log_debug("\nEnter u_m3_m3_con_get");
log_debug_append("\n key 'a' = %d", k_m3_a);
log_debug_append("\n GETCB_GET2_USER_DATA_REF(%p)"
"\n GETCB_GET2_USER_DATA_INDEX(%u)\n",
GETCB_GET2_USER_DATA_REF(get2cb),
GETCB_GET2_USER_DATA_INDEX(get2cb));
}
// ...
}
Function 'u_m3_m3_list_get':
status_t u_m3_m3_list_get (
getcb_get2_t *get2cb,
uint8 k_m3_a,
boolean a_fixed,
boolean a_present)
{
if (LOGDEBUG) {
log_debug("\nEnter u_m3_m3_list_get");
log_debug_append("\n key 'a' = %d, fixed=%s, present=%s",
k_m3_a,
a_fixed ? NCX_EL_TRUE : NCX_EL_FALSE,
a_present ? NCX_EL_TRUE : NCX_EL_FALSE);
log_debug_append("\n GETCB_GET2_USER_DATA_REF(%p)"
"\n GETCB_GET2_USER_DATA_INDEX(%u)\n",
GETCB_GET2_USER_DATA_REF(get2cb),
GETCB_GET2_USER_DATA_INDEX(get2cb));
}
// ...
}
Increment the user_data_index in the list GET2 callback
In this example, the user_data_index is simply incremented each time the first entry is requested.
Note that it is possible for a client to construct a filter retieval request where the first list entry will not be requested.
It is expected that real SIL-SA instrumentation code will use internal state to manage the user data fields.
/* set the USER_DATA the first time the GET2 is called
* The first call will be with no keys, requesting
* the first entry
*/
if (!a_present) {
user_data_index++;
GETCB_GET2_USER_DATA_REF(get2cb) = &user_data_index;
GETCB_GET2_USER_DATA_INDEX(get2cb) = user_data_index;
}
GET2 Callback Container Example
Consider this simplified example, that represents GET2 callback for the “interface-state” container.
container interfaces-state {
config false;
leaf in-errors { type uint32; }
// rest of leafs removed
}
The following SIL code shows a GET2 callback which returns the “interfaces-state” container element with one child leaf node.
-
void getcb_add_return_val(getcb_get2_t *get2cb, val_value_t *val)
Add a return val to a get2cb return_valQ.
Important API needed by GET2 callbacks to add return values
- Parameters:
get2cb -- get2 control block to use
val -- value to add; pass off memory; will be cleaned when the get2cb is freed
The 'agt_make_leaf2' API is used in this example.
-
val_value_t *agt_make_leaf2(obj_template_t *parentobj, const xmlChar *modname, const xmlChar *leafname, const xmlChar *leafstrval, status_t *res)
make a val_value_t struct for a specified leaf or leaf-list
Similiar to agt_make_leaf except this API allows augmented child nodes to be accessed.
See also
- Parameters:
parentobj -- parent object to find child leaf object
modname -- name of module defining leaf (may be NULL to pick parent)
leafname -- name of leaf to find (namespace hardwired)
leafstrval -- string version of value to set for leaf
res -- [out] address of return status; *res return status
- Returns:
malloced value struct or NULL if some error
/********************************************************************
* FUNCTION get2_container_interfaces
*
* Path: /interfaces-state
*
* INPUTS:
* scb == session control block making the request
* msg == incoming XML message header
* get2cb == get2 control block for this callback request
*
* OUTPUTS:
* return_valQ is filled with malloced val_value_t nodes
*
* RETURNS:
* status:
* NO_ERR if executed OK and found OK
* ERR_NCX_NO_INSTANCE warning if no instance found
*
********************************************************************/
static status_t
get2_container_interfaces (ses_cb_t *scb,
xml_msg_hdr_t *msg,
getcb_get2_t *get2cb)
{
(void)scb;
(void)msg;
/* 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:
return SET_ERROR(ERR_INTERNAL_VAL);
}
/* get the template for the “interfaces-state” container */
obj_template_t *obj = GETCB_GET2_OBJ(get2cb);
status_t res = NO_ERR;
/* pretend leaf 'in-errors' is present */
val_value_t *retval =
agt_make_leaf2(obj,
EXAMPLE_MODNAME,
(const xmlChar *)"in-errors",
(const xmlChar *)"8",
&res);
if (retval) {
getcb_add_return_val(get2cb, retval);
} else {
return res;
}
return res;
} /* get2_container_interfaces */
To ensure that the GET2 callbacks are working as expected an application may retrieve running configuration and device state information as follows:
<?xml version="1.0" encoding="UTF-8"?>
<rpc message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<get>
<filter type="subtree">
<interfaces-state xmlns="http://yumaworks.com/ns/ietf-interfaces-example" />
</filter>
</get>
</rpc>
The server may reply with:
<rpc-reply message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<interfaces-state xmlns="http://yumaworks.com/ns/ietf-interfaces-example">
<in-errors>8</in-errors>
</interfaces-state>
</rpc-reply>
GET2 Callback List Example
Consider this simplified example, that demonstrates a GET2 callback for the “interface” list.
container interfaces-state {
config false;
list interface {
key "ip";
leaf ip {
type inet:ip-prefix;
}
leaf name {
type string;
mandatory true;
}
}
}
The GET2 callbacks for “list” elements must fill in the return key values. All additional leafs are optional to fill in. Any other descendant complex elements, such other lists, containers, or choice nodes must have their own callbacks functions.
-
void getcb_add_return_key(getcb_get2_t *get2cb, val_value_t *val)
Add a return keyval to a get2cb return_keyQ.
This is an important GET2 callback API for a list node The list must add return keys in order to continue the walk through all list entries
- Parameters:
get2cb -- get2 control block to use
val -- value to add; pass off memory here
/********************************************************************
* FUNCTION get2_list_interface
*
* Path: /interfaces-state/interface
*
* INPUTS:
* scb == session control block making the request
* msg == incoming XML message header
* get2cb == get2 control block for this callback request
*
* OUTPUTS:
* return_valQ is filled with malloced val_value_t nodes
* return_keyQ is filled with any new keys added for this entry
*
* RETURNS:
* status:
* NO_ERR if executed OK and found OK
* ERR_NCX_NO_INSTANCE warning if no instance found
*
********************************************************************/
static status_t
get2_list_interface (ses_cb_t *scb,
xml_msg_hdr_t *msg,
getcb_get2_t *get2cb)
{
(void)scb;
(void)msg;
uint32 max_entries = GETCB_GET2_MAX_ENTRIES(get2cb);
(void)max_entries;
boolean getnext = FALSE;
/* check the callback mode type */
getcb_mode_t cbmode = GETCB_GET2_CBMODE(get2cb);
switch (cbmode) {
case GETCB_GET_VALUE:
break;
case GETCB_GETNEXT_VALUE:
getnext = TRUE;
break;
default:
return SET_ERROR(ERR_INTERNAL_VAL);
}
/* get the list and child objects */
obj_template_t *obj = GETCB_GET2_OBJ(get2cb);
obj_template_t *key_obj =
obj_find_child(obj,
EXAMPLE_MODNAME,
(const xmlChar *)"ip");
/* init leaf values for the list */
const xmlChar *name = NULL;
const xmlChar *ret_name = NULL;
boolean name_fixed = FALSE;
/* check the keyQ for the specific list instance requested */
val_value_t *name_key = getcb_find_key(get2cb, key_obj);
/* pretend there are 3 values */
#define IP1 (const xmlChar *)"10.10.10.1/16"
#define IP2 (const xmlChar *)"10.10.10.2/16"
#define IP3 (const xmlChar *)"10.10.10.3/16"
/* specific list instance requested, check if it is valid */
if (name_key && VAL_STR(name_key)) {
name = VAL_STR(name_key);
int32 ret = xml_strcmp(name, IP2);
if (ret > 0) {
return ERR_NCX_NO_INSTANCE;
}
/* specific list instance requested, set the flag */
name_fixed = VAL_IS_FIXED_VALUE(name_key);
}
/* set to true if there are more instances of this object to retrieve;
* false otherwise
*/
boolean moreflag = TRUE;
/* check validity of keys present */
if (getnext) {
if (name_fixed) {
return ERR_NCX_NO_INSTANCE;
}
/* adjust the key to find the next entry after
* the specified value
*/
if (!name) {
ret_name = IP1; // return first entry [0]
} else {
/* find the correct entry to retrieve */
int32 ret = xml_strcmp(name, IP1);
if (ret < 0) {
ret_name = IP1;
} else if (ret == 0) {
ret_name = IP2;
} else {
/* check IP2 */
ret = xml_strcmp(name, IP2);
if (ret < 0) {
ret_name = IP2;
} else if (ret == 0) {
ret_name = IP3;
moreflag = FALSE;
} else {
/* assume IP3 */
ret_name = IP3;
moreflag = FALSE;
}
}
}
} else {
if (name) {
/* get a specified instance */
boolean name_ok = FALSE;
if (!xml_strcmp(name, IP1)) {
name_ok = TRUE;
} else if (!xml_strcmp(name, IP2)) {
name_ok = TRUE;
} else if (!xml_strcmp(name, IP3)) {
name_ok = TRUE;
moreflag = FALSE;
}
if (name_ok) {
ret_name = name;
} else {
return ERR_NCX_NO_INSTANCE;
}
} else {
// else no keys == get-first
ret_name = IP1;
}
}
GETCB_GET2_MATCH_TEST_DONE(get2cb) = FALSE;
if (ret_name == NULL) {
return ERR_NCX_NO_INSTANCE;
}
GETCB_GET2_MORE_DATA(get2cb) = moreflag;
status_t res = NO_ERR;
/* if we get here then the index is valid
*
* create a return_keyQ node for the name key value
*/
val_value_t *return_val =
agt_make_leaf2(obj,
EXAMPLE_MODNAME,
(const xmlChar *)"ip",
ret_name,
&res);
if (res == NO_ERR) {
if (name_fixed) {
VAL_SET_FIXED_VALUE(return_val);
}
getcb_add_return_key(get2cb, return_val);
}
/* Also, pretend leaf 'name' is present
* Here, might be another key name validation that will dictate what “name”
* value to use based on key value.
* Note, “name” leaf is a mandatory=true leaf, so it must be present.
*/
val_value_t *retval =
agt_make_leaf2(obj,
EXAMPLE_MODNAME,
(const xmlChar *)"name",
(const xmlChar *)"interface-name",
&res);
if (retval) {
getcb_add_return_val(get2cb, retval);
} else {
return res;
}
return NO_ERR;
} /* get2_list_interface */
Refer to the GET2 Access Functions section for other available functions to access GET2 control block.
To ensure that the GET2 callbacks are working as expected an application can retrieve running configuration and device state information as follows:
<?xml version="1.0" encoding="UTF-8"?>
<rpc message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<get>
<filter type="subtree">
<interfaces-state xmlns="http://yumaworks.com/ns/ietf-interfaces-example">
<interface>
<ip>10.10.10.1/16</ip>
</interface>
</interfaces-state>
</filter>
</get>
</rpc>
The server may reply with:
<rpc-reply message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<interfaces-state xmlns="http://yumaworks.com/ns/ietf-interfaces-example">
<interface>
<ip>10.10.10.1/16</ip>
<name>interface-name</name>
</interface>
</interfaces-state>
</rpc-reply>
If no key value is specified in the request or the request subtree filter target is “interface-state” parent container, then the server will try to output all the interfaces and its children:
<?xml version="1.0" encoding="UTF-8"?>
<rpc message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<get>
<filter type="subtree">
<interfaces-state xmlns="http://yumaworks.com/ns/ietf-interfaces-example" />
</filter>
</get>
</rpc>
The server may reply with:
<rpc-reply message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<interfaces-state xmlns="http://yumaworks.com/ns/ietf-interfaces-example">
<interface>
<ip>10.10.10.1/16</ip>
<name>interface-name</name>
</interface>
<interface>
<ip>10.10.10.2/16</ip>
<name>interface-name2</name>
</interface>
<interface>
<ip>10.10.10.3/16</ip>
<name>interface-name3</name>
</interface>
</interfaces-state>
</rpc-reply>
GET2 Callback Choice Example
A GET2 callback function for a YANG Choice is expected to return the
name of the active choice, and any terminal nodes from the active case.
It can also return ERR_NCX_NO_INSTANCE
if no active case is set.
Consider this simplified example, that represents GET callback for the node type “choice”.
choice type {
case interface { // active case
leaf interface {
type if:interface-ref;
}
}
case case-network {
leaf next-hop-host {
type inet:ip-address;
}
}
}
-
status_t getcb_set_active_case(getcb_get2_t *get2cb, const xmlChar *active_case_modname, const xmlChar *active_case)
Set the active case to the specified object.
- Parameters:
get2cb -- get2 control block to use
active_case_modname -- module name to set
active_case -- case name to set
- Returns:
status
The following C API code shows a simple GET2 callback function for the data model, which returns the name of the active case and child terminal nodes in the active case when it is requested.
/********************************************************************
* FUNCTION get2_choice_type
*
* Path: /type
*
* INPUTS:
* scb == session control block making the request
* msg == incoming XML message header
* get2cb == get2 control block for this callback request
*
* OUTPUTS:
* return_valQ is filled with malloced val_value_t nodes
* the a backptr to a constant string containing the case
* name that is active
*
* RETURNS:
* status:
* NO_ERR if executed OK and found OK
* ERR_NCX_NO_INSTANCE warning if no instance found
*
********************************************************************/
static status_t
get2_choice_type (ses_cb_t *scb,
xml_msg_hdr_t *msg,
getcb_get2_t *get2cb)
{
(void)scb;
(void)msg;
status_t res = NO_ERR;
/* 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:
return SET_ERROR(ERR_INTERNAL_VAL);
}
/* get the active case
* callback based on instances in the system
* It may be NULL if no active case
*/
const xmlChar *active_case_modname = NULL;
const xmlChar *active_case = (const xmlChar *)"interface";
/* Set the active case to the specified object */
res = getcb_set_active_case(get2cb,
active_case_modname,
active_case);
if (active_case == NULL) {
return res;
}
/* get the template for the active case */
obj_template_t *choice_obj = GETCB_GET2_OBJ(get2cb);
if (choice_obj == NULL) {
return ERR_NCX_NOT_FOUND;
}
obj_template_t *case_obj =
obj_find_child(choice_obj,
active_case_modname,
active_case);
if (case_obj == NULL) {
return ERR_NCX_NOT_FOUND;
}
/* return all the leaf and leaf-list instances that are child nodes
* of the active case.
*/
val_value_t *chval = NULL;
if (!xml_strcmp(active_case, (const xmlChar *)"interface")) {
/* pretend leaf 'interface' is present */
chval = agt_make_leaf2(case_obj,
active_case_modname,
(const xmlChar *)"interface",
(const xmlChar *)"ethernet1/1/1",
&res);
if (chval) {
getcb_add_return_val(get2cb, chval);
} else {
return res;
}
} else {
res = SET_ERROR(ERR_INTERNAL_VAL);
}
return res;
} /* get2_choice_type */
Note that any leaf or leaf-list nodes that are in nested within other “choice” statements within the active case are not returned . These are considered complex objects and the caller will ask for nested choice, list, and container nodes in a separate call .
container top {
choice complex-type {
case interface { // active case
choice new-choice {
leaf interface {
type if:interface-ref;
}
leaf test-interface {
type if:interface-ref;
}
}
}
case case-network {
leaf next-hop-host {
type inet:ip-address;
}
}
}
}
In the example above, the GET2 callback for 'complex-choice'
would be called first and the active case name interface
would
be returned without a terminal node.
Then the GET2 callback for 'new-choice' would be invoked and
this callback will return the name of the active case
(interface
or test-interface
), and the child leaf for that case.
Refer to the GET2 Access Functions section for other available functions to access GET2 control block.
GET2 Callback Example for To ensure that the GET2 callback is working as expected an application can retrieve running configuration and device state information as follows:
<?xml version="1.0" encoding="UTF-8"?>
<rpc message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<get>
<filter type="subtree">
<top xmlns="http://yumaworks.com/ns/interfaces" />
</filter>
</get>
</rpc>
The server may reply with:
<rpc-reply message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<top xmlns="http://yumaworks.com/ns/interfaces">
<interface>ethernet1/1/1</interface>
</top>
</rpc-reply>
GET2 Callback Example For Leaf
Consider this simplified example, that represents GET callback for a “leaf” node.
container top {
leaf get2-leaf {
config false;
type int32;
}
}
}
Note that only leaf or leaf-list nodes that are top level nodes or the parent node is a config=true node will require a GET2 callback.
The following C API code shows a simple GET2 callback function for the data model, which returns the leaf value when it is requested.
/* start value for leaf /get2-leaf */
static int32 leaf_val = 42;
static status_t
get2_leaf_type (ses_cb_t *scb,
xml_msg_hdr_t *msg,
getcb_get2_t *get2cb)
{
(void)scb;
(void)msg;
/* 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:
return SET_ERROR(ERR_INTERNAL_VAL);
}
/* get the real value from the system here.
* But in this example just increment the value by 1
*/
int32 retval = leaf_val++;
/* return the leaf in the static return_val struct */
obj_template_t *obj = GETCB_GET2_OBJ(get2cb);
val_value_t *return_val = GETCB_GET2_RETURN_VAL(get2cb);
val_init_from_template(return_val, obj);
/* Do not use a VAL_ macro unless sure type is correct */
if (VAL_TYPE(return_val) == NCX_BT_INT32) {
VAL_INT32(return_val) = retval;
return NO_ERR;
}
/* object not int32!!! */
return ERR_NCX_OPERATION_FAILED;
} /* get2_leaf_type */
Refer to the GET2 Access Functions section for other available functions to access GET2 control block.
In the above example, the code generates the value based on the static value that is incremented after each retrieval. An agent can generate the value based on the real device statistical data or based on the system's statistical data, etc.
To ensure that the GET2 callback is working as expected an application can retrieve running configuration and device state information as follows:
<?xml version="1.0" encoding="UTF-8"?>
<rpc message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<get>
<filter type="subtree">
<get2-leaf xmlns="http://yumaworks.com/ns/interfaces" />
</filter>
</get>
</rpc>
The server may reply with:
<rpc-reply message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<get2-leaf xmlns="http://yumaworks.com/ns/interfaces">42</get2-leaf>
</rpc-reply>
GET2 Callback Example for Leaf-List
A GET2 callback for a leaf-list is similar to the callback for a leaf, except multiple values can be returned, instead of one value. The server will return the data in the same order as returned by the callback function.
Consider this simplified example, that represents a GET2 callback for a “leaf-list” node.
leaf-list get2-leaf-list {
config false;
type int32;
}
The following C API code shows a simple GET2 callback function for the same data model, which returns the leaf-list values when requested.
static status_t
get2_leaflist_type (ses_cb_t *scb,
xml_msg_hdr_t *msg,
getcb_get2_t *get2cb)
{
(void)scb;
(void)msg;
status_t res = NO_ERR;
/* 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:
return SET_ERROR(ERR_INTERNAL_VAL);
}
obj_template_t *obj = GETCB_GET2_OBJ(get2cb);
/* always return all the leaf-list instances
* 3 entries [53, 67, 92]
*/
val_value_t *retval =
val_make_simval_obj(obj, (const xmlChar *)"53", &res);
if (retval) {
getcb_add_return_val(get2cb, retval);
}
if (res == NO_ERR) {
retval = val_make_simval_obj(obj, (const xmlChar *)"67", &res);
if (retval) {
getcb_add_return_val(get2cb, retval);
}
}
if (res == NO_ERR) {
retval = val_make_simval_obj(obj, (const xmlChar *)"92", &res);
if (retval) {
getcb_add_return_val(get2cb, retval);
}
}
return res;
} /* get2_leaflist_type */
In the above example, the code generates the value based on the static value that does not fluctuate after each retrieval. An agent can generate the value based on the real device statistical data or based on the system's statistical data, etc.
To ensure that the GET2 callback is working as expected an application can retrieve running configuration and device state information as follows:
<?xml version="1.0" encoding="UTF-8"?>
<rpc message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<get>
<filter type="subtree">
<get2-leaf-list xmlns="http://yumaworks.com/ns/interfaces" />
</filter>
</get>
</rpc>
The server may reply with:
<rpc-reply message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<get2-leaf-list xmlns="http://yumaworks.com/ns/interfaces">53</get2-leaf-list>
<get2-leaf-list xmlns="http://yumaworks.com/ns/interfaces">67</get2-leaf-list>
<get2-leaf-list xmlns="http://yumaworks.com/ns/interfaces">92</get2-leaf-list>
</rpc-reply>
GET2 Access Functions
There are several GET2 mode specific high-level Transaction access and
management utilities in ncx/getcb.h
.
The following functions are described elsewhere in this document:
getcb_add_return_val
getcb_add_return_key
getcb_set_active_case
getcb_first_requested_child
getcb_next_requested_child
getcb_add_return_aioQ
-
val_value_t *getcb_find_return_val(getcb_get2_t *get2cb, obj_template_t *obj)
Find a return val in the get2cb return_valQ.
- Parameters:
get2cb -- get2 control block to use
obj -- object type to find
-
val_value_t *getcb_find_return_val2(getcb_get2_t *get2cb, xmlns_id_t obj_nsid, const xmlChar *obj_name)
Find a return val in the get2cb return_valQ use { NSID, NAME } instead of object pointer.
- Parameters:
get2cb -- get2 control block to use
obj_nsid -- object module namespace to find
obj_name -- object local-name to find
- Returns:
pointer to found value or NULL if not found
-
val_value_t *getcb_find_next_return_val(getcb_get2_t *get2cb, val_value_t *curval)
Find the next matching return val in the get2cb return_valQ.
- Parameters:
get2cb -- get2 control block to use
curval -- current value pointer
- Returns:
next return value or NULL if none
-
val_value_t *getcb_find_key(getcb_get2_t *get2cb, obj_template_t *obj)
Find an input keyval in the get2cb keyQ.
Use an object template to search the return value queue
- Parameters:
get2cb -- get2 control block to use
obj -- key object type to find
- Returns:
pointer to the found key value or NULL if not found
-
val_value_t *getcb_find_key_str(getcb_get2_t *get2cb, const xmlChar *modname, const xmlChar *objname)
Find an input keyval in the get2cb keyQ with a string.
Use the object name string to search the return value queue
- Parameters:
get2cb -- get2 control block to use
modname -- module name of the key object
objname -- object name of the key object
- Returns:
pointer to the found key value or NULL if not found
-
val_value_t *getcb_find_match(getcb_get2_t *get2cb, obj_template_t *obj)
Find an input keyval in the get2cb matchQ.
- Parameters:
get2cb -- get2 control block to use
obj -- object type to find
- Returns:
value struct for the match node or NULL if not found
-
void getcb_add_key(getcb_get2_t *get2cb, val_value_t *val)
Add a keyval to a get2cb keyQ.
This is NOT the return key Q! This is not a user API for a GET2 callback
- Parameters:
get2cb -- get2 control block to use
val -- value to add
-
void getcb_add_match(getcb_get2_t *get2cb, val_value_t *val)
Add a match node to a get2cb matchQ.
This is not a user API for a GET2 callback
- Parameters:
get2cb -- get2 control block to use
val -- value to add
-
status_t getcb_add_select(getcb_get2_t *get2cb, const xmlChar *modname, const xmlChar *objname)
Add a select node to a get2cb matchQ.
This is not a user API for a GET2 callback
- Parameters:
get2cb -- get2 control block to use
modname -- module name of the select node
objname -- object name of the select node
- Returns:
status
-
val_value_t *getcb_find_return_key(getcb_get2_t *get2cb, obj_template_t *obj)
Find a return keyval in the get2cb return_keyQ.
- Parameters:
get2cb -- get2 control block to use
obj -- object type to find
- Returns:
value node for this object or NULL if not found
-
val_value_t *getcb_find_return_key2(getcb_get2_t *get2cb, xmlns_id_t obj_nsid, const xmlChar *obj_name)
Find a return keyval in the get2cb return_keyQ Use { NSID, NAME } instead of object pointer.
- Parameters:
get2cb -- get2 control block to use
obj_nsid -- namespace ID of object to find
obj_name -- name of object to find
- Returns:
value node for this return key or NULL if not found
-
void getcb_move_return_keys(getcb_get2_t *get2cb)
Move the return keys to the keyQ replacing the nodes in the keyQ if already there.
- Parameters:
get2cb -- get2 control block to use
-
void getcb_undo_move_return_keys(getcb_get2_t *get2cb)
Move back the return keys from the keyQ.
- Parameters:
get2cb -- get2 control block to use
-
void getcb_clean_return_data(getcb_get2_t *get2cb)
Clean the return data in the return_val and return_valQ.
- Parameters:
get2cb -- get2 control block to clean
-
status_t getcb_handle_acmtest(ses_cb_t *scb, xml_msg_hdr_t *msg, val_nodetest_fn_t testfn, getcb_get2_t *get2cb)
check the access control and testfn callback for a node that would have it skipped because the write_full_check_val function is skipped
- Parameters:
scb -- session control block
msg -- xml_msg_hdr_t in progress
testfn -- callback function to use, NULL if not used
get2cb -- get2 control block to use
- Returns:
status: ERR_NCX_SKIPPED if tests fail; NO_ERR if they pass
-
getcb_keyval_t *getcb_new_keyval(const xmlChar *keyval)
Create a new GET2 keyval holder.
- Parameters:
keyval -- key value string
- Returns:
pointer to initialized keyval, or NULL if malloc error
-
getcb_keyval_t *getcb_new_keyval2(val_value_t *keynode, obj_template_t *keyobj, boolean fixed_value)
Create a new Get2 keyval holder using val backptr.
- Parameters:
keynode -- val_value_t node for key value
keyobj -- object template for real object
fixed_value -- TRUE if a fixed value for getnext purposes
- Returns:
pointer to initialized keyval, or NULL if malloc error
-
void getcb_free_keyval(getcb_keyval_t *keyval)
Free a GET2 keyval.
- Parameters:
keyval -- keyval to free
-
void getcb_clean_keyvalQ(dlq_hdr_t *que)
Free all the Get2 keyvals from a dlq_hdr.
- Parameters:
que -- Q of getcb_keyval_t to clean
-
void getcb_dump_get2cb(getcb_get2_t *get2cb)
Print the interesting fields in a get2cb.
- Parameters:
get2cb -- get2 control block to use
-
boolean getcb_need_get2(obj_template_t *curobj, obj_template_t *reqobj)
check if the node has a get2 callback or in a choice/case subtree that has get2 callback
- Parameters:
curobj -- ceiling object — data parent of 'reqobj'
reqobj -- target object requested that may have get2 cb
- Returns:
TRUE if get2 applies; FALSE if get2 does not apply
-
boolean getcb_need_get2_ex(obj_template_t *curobj, obj_template_t *reqobj, uint32 *choicecnt, obj_template_t **top_choice)
check if the node has a get2 callback or in a choice/case subtree that has get2 callback
- Parameters:
curobj -- ceiling object — data parent of 'reqobj'
reqobj -- target object requested that may have get2 cb
choicecnt -- [out]
address of return choice count
*choicecnt number of implied choices that need to be retrieved
top_choice -- [out]
address of return top_choice
*top_choice topmost choice that needs to be retrieved
- Returns:
TRUE if get2 applies; FALSE if get2 does not apply
-
status_t getcb_add_return_aio_buff(getcb_get2_t *get2cb, const xmlChar *buffer)
Add a return buffer to a get2cb control block and set encoding type.
- Parameters:
get2cb -- get2 control block to use
buffer -- buffer used in the callback
- Returns:
status
GETBULK Support
The netconfd-pro server supports a YANG list iterator operation called <get-bulk>. It is defined in yumaworks-getbulk.yang.
The GET2 callback is used for GETBULK support. There are parameters passed to the callback function that indicate the bulk parameters to use. Data is returned from the callback to the server in a slightly different way than a "plain" GET2 callback that returns one entry at a time.
The maximum number of entries a GET2 callback should return is configured with the --max-getbulk parameter. It is used for the 'max-entries' field, which is passed in the <get-bulk> request.
No error is generated in the server if more entries are returned, but extra entries may not be returned to the client.
The GET2 callback is not required to return more than one entry.
The max-entries field can be ignored by the GET2 callback.
The --max-getbulk CLI parameter can be used to change the 'max-entries' value. The default is '10'.
The 'agt_sil_getbulk_max' agt_profile field contains the configured value at run-time. This field is used to set the 'max-entries' parameter in the <get-bulk> request.
This parameter is only set for YANG list nodes. A leaf-list callback always returns all instances in 1 request. All other data node callback types are expected to return 1 entry.
If max-entries is set to zero, then the GET2 callback can return as many entries as desired.
Existing GET2 code will check the max_entries field and return ERR_NCX_OPERATION_FAILED if it is not set to '1'. This code needs to be removed in order to use GETBULK. New SIL code generated with yangdump-sdk does not generate this check on max_entries any more.
Understanding --max-getbulk and max_entries
--max-getbulk parameter:
The --max-getbulk parameter specifies the maximum number of entries a GET2 callback should return during a GETBULK operation.
The default value is 10 (or 512 if set to 0).
If set to 0, an internal limit of 512 is used. In this case, agt_sil_getbulk_max defaults to 512. The max_entries(GETCB_GET2_MAX_ENTRIES) parameter in the GET2 Control Block is set to 0, indicating that the callback can return as many entries as desired, up to the internal limit.
Behavior details:
If the client's 'count' exceeds the server's --max-getbulk limit, the server returns an error with the message "too many entries".
If the client's 'count' is within the allowed range, max_entries(GETCB_GET2_MAX_ENTRIES) in the GET2 Control Block is set to the client's 'count'.
If the GET2 callback returns more entries than max_entries/count, the server processes entries up to the 'count' entries, discarding any excess.
In order to use the GETBULK API, a GET2 callback MUST add a call to 'getcb_finish_getbulk_entry' function after the entry is completed. The general code for a GET2 callback stays the same, except that the callback can loop through its code to find the next GETNEXT entry multiple times.
The first instance of a list is requested with a GET request, and none of the keys will be present. This is an indication to the list callback that the first entry is being requested. The GET2 callback can return up to 'max-entries' getbulk instances. The next request will be a GETNEXT request, starting where the last getbulk entry left off.
Processing List Keys for GETNEXT and GETBULK
There are 2 types of keys passed to a GET2 function in a “u_” callback file for a list:
ancestor keys: these key values are always set and always fixed
local keys: these key values are for the target list object.
key value: The value of they key in its base type; Only valid if foo_present flag is true
foo_present: true if the foo local key leaf value is valid
foo_fixed: true if the foo local key leaf value is fixed. This indicates that a content-match filter for that key leaf value is being processed. If true, then the GET2 callback MUST NOT increment the associated key value. Only entries matching the fixed key value are to be returned.
If all of the local keys are set and the associated “foo_fixed” leaf is also set, then the server is requesting a single list entry. Do not return multiple entries in the case, even if max-entries is greater than 1.
GETBULK Operation Examples
This simple example shows how the GETBULK procedure
may be supported for a list /top-state/A
.
GETBULK Example YANG Module
The following YANG file is used in this example:
module t104 {
namespace "http://netconfcentral.org/ns/t104";
prefix t104;
revision "2015-11-04";
container top-state {
config false;
list A {
key X;
leaf X { type int32; }
leaf Y { type int32; }
leaf Z { type int32; }
}
}
}
GETBULK Example C Files
There are 2 different versions of this test module implemented for SIL and SIL-SA testing.
Refer to the SIL-SA file
/usr/share/yumapro/src/get2-test/src/get2-test.c
in the YumaPro installation.Refer to the SIL file
/usr/share/yumapro/src/get2-test-local/src/get2-test.c
in the YumaPro installation.
GETBULK Example Callback Function
The following SIL code shows how GETBULK might be implemented for the
list /top-state/A.
/********************************************************************
* FUNCTION u_t104_top_state_A_get
*
* Get database object callback for list A
* Path: /top-state/A
* Fill in 'get2cb' response fields
*
* INPUTS:
* see ncx/getcb.h for details (getcb_fn2_t)
*
* RETURNS:
* error status
********************************************************************/
status_t u_t104_top_state_A_get (
getcb_get2_t *get2cb,
int32 k_top_state_A_X,
boolean X_fixed,
boolean X_present)
{
boolean getnext = FALSE;
/* check the callback mode type */
getcb_mode_t cbmode = GETCB_GET2_CBMODE(get2cb);
switch (cbmode) {
case GETCB_GET_VALUE:
break;
case GETCB_GETNEXT_VALUE:
getnext = TRUE;
break;
default:
return SET_ERROR(ERR_INTERNAL_VAL);
}
obj_template_t *obj = GETCB_GET2_OBJ(get2cb);
uint32 max_entries = GETCB_GET2_MAX_ENTRIES(get2cb);
if (max_entries == 0) {
max_entries = 10;
}
obj_template_t *X_obj =
obj_find_child(obj, NULL, (const xmlChar *)"X");
obj_template_t *Y_obj =
obj_find_child(obj, NULL, (const xmlChar *)"Y");
obj_template_t *Z_obj =
obj_find_child(obj, NULL, (const xmlChar *)"Z");
boolean getbulk_mode = !X_fixed;
/* For GET, find the entry that matches the key values
* For GETNEXT, find the entry that matches the next key value
* If the 'present' flag is false then return first key instance
* If the 'fixed' flag is true then no GETNEXT advance for the key
* Create a new return key val_value_t, then getcb_add_return_key */
#define MAX_X 42
int32 X_val = 0;
if (X_present) {
X_val = k_top_state_A_X;
} else {
X_val = 1;
}
if (getnext) {
++X_val;
}
if ((X_val == 0) || (X_val > MAX_X)) {
return ERR_NCX_NO_INSTANCE;
}
int32 seed_num = X_val * 100;
uint32 entry_count = 0;
boolean done = false;
while (!done) {
val_value_t *X_return_val = val_new_value();
if (X_return_val == NULL) {
return ERR_INTERNAL_MEM;
}
val_init_from_template(X_return_val, X_obj);
VAL_INT32(X_return_val) = X_val;
getcb_add_return_key(get2cb, X_return_val);
if (GETCB_GET2_FIRST_RETURN_KEY(get2cb) == NULL) {
return ERR_NCX_NO_INSTANCE;
}
/* 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;
/* For GETNEXT, set the more_data flag true if not sure */
boolean more_data = (X_val < MAX_X);
/**** SET more_data FLAG ****/
GETCB_GET2_MORE_DATA(get2cb) = more_data;
/* 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)) {
/* Retrieve the value of this terminal node and
* add with getcb_add_return_val */
/* real code would find the current list entry
* and then get the Y and Z leafs out of that entry
* This code just returns random numbers
*/
val_value_t *childval = NULL;
/* leaf Y (int32) */
if (childobj == Y_obj) {
childval = val_new_value();
if (childval == NULL) {
return ERR_INTERNAL_MEM;
}
val_init_from_template(childval, childobj);
srand(seed_num++);
VAL_INT32(childval) = rand();
getcb_add_return_val(get2cb, childval);
} else if (childobj == Z_obj) {
/* leaf Z (int32) */
childval = val_new_value();
if (childval == NULL) {
return ERR_INTERNAL_MEM;
}
val_init_from_template(childval, childobj);
srand(seed_num++);
VAL_INT32(childval) = rand();
getcb_add_return_val(get2cb, childval);
}
}
if (getbulk_mode) {
getcb_finish_getbulk_entry(get2cb);
/* make sure both numbers get incremented */
++entry_count;
++X_val;
if ((entry_count == max_entries) || (X_val > MAX_X)) {
done = true;
}
} else {
done = true;
}
}
return NO_ERR;
} /* u_t104_top_state_A_get */
GETBULK KnowledgeBase FAQs
All In One GET2 Callback
This section describes All In One (AIO) GET2 mechanism that allows to call a single GET2 callback for the whole Subtree. An AIO callback can return data to the server in 3 different formats:
XML
JSON
AIO GET2 callbacks follow exactly the same template as regular GET2 callbacks, they use the same registration API and are getting invoked the same way for SIL and SIL-SA as regular GET2 callbacks. The main difference is that there will not be any callback invocations and there will not be any callbacks at all for any of AIO node children.
All In One GET2 support:
Supported only for container and list nodes
YANG extension must be specified for the top AIO node
Supported for SIL and SIL-SA
Supported for XPath and Subtree filtering
Supported for RESTCONF retrieval
Supported for <get-bulk> operation
Supported for NMDA <get-data> operation for "operational" datastore
Auto-generation code support
Must be built with --sil-get2 flag during make_sil_dir_pro
The entire subtree would be expected in one retrieval in one callback invocation. That’s if the callback is registered for a container, any complex nodes within this container will not have any extra callbacks, they will be handled by the top AIO container callback.
sil-aio-get2 YANG Extension
With help of a new YANG extension, the server will set specific flags to the objects to identify them as a special case retrieval. This extension would be used in a container or a list to force the server to treat that data subtree as a AIO node for GET2.
The yangdump-sdk auto-generation code does not auto generate callbacks for children of the top All In One parent. The callbacks will be generated only for the top nodes that have the extension specified. The extension definition looks as follows:
extension sil-aio-get2 {
description
"Used within a data definition statement to define
the GET2 retrieval mechanism.
This extension affects the descendant data nodes.
This extension can be used in a container or list
to force the server to treat that data subtree as
a terminal node for GET2.
The entire subtree would be expected in one retrieval
in one callback invocation.
The entire subtree can be specified in the JSON
or XML buffer that will be used for return values.
The server will parse and handle the buffer and process
retrieval based on the provide JSON or XML encoded buffer.
The 'parmstr' argument can specify the encoding that will
be used in the callback. Available options are:
- xml: XML element in a buffer is expected in return value
- json: JSON object in a buffer is expected in return value
- val: val_value_t tree is expected in return value
";
argument parmstr;
}
The following YANG snippet shows how the extension can be used to cause an AIO callback function to be generated for the 'nonconfig-list' data node:
// Need to import yumaworks-extensions
import yumaworks-extensions { prefix ywx; }
// Use ywx:sil-api-get2 statement as needed
container get3-config-cont {
presence "Presence container";
list config-list {
key name;
leaf name { type string; }
list nonconfig-list { // Regular GET2 CB
config false;
key name;
leaf name { type string; }
list nonconfig-list2 { // All in One GET2 CB
ywx:sil-aio-get2 "val";
key name;
leaf name { type string; }
list nonconfig-list3 { // No callback
key name;
leaf name { type string; }
container int-con { // No callback
leaf int-con-leaf { type int32; }
}
}
}
}
}
}
All In One API Functions
The following API functions are used in this example:
-
obj_template_t *getcb_first_requested_child(getcb_get2_t *get2cb, obj_template_t *parent_obj)
Check if the specified object has any terminal nodes that need to be returned for a get2 request.
Return the first one
The get2cb will be examined:
boolean flags
select-node queue
Any content-match nodes will be returned but no filtering of these objects will be done
Any key leaf nodes will be skipped; Processing of key leafs is done first and is never skipped
- Parameters:
get2cb -- get2 control block to use
parent_obj -- target object usually the obj from get2cb but could be the active case object for a choice callback
- Returns:
pointer to first requested child terminal object
NULL if none
-
obj_template_t *getcb_next_requested_child(getcb_get2_t *get2cb, obj_template_t *curchild)
Check if the specified object has any more terminal nodes that need to be returned for a get2 request.
Return the next one
The get2cb will be examined:
boolean flags
select-node queue
Any content-match nodes will be returned but no filtering of these objects will be done
Any key leaf nodes will be skipped; Processing of key leafs is done first and is never skipped
- Parameters:
get2cb -- get2 control block to use
curchild -- current child; start search with next sibling
- Returns:
pointer to next requested child terminal object
NULL if none
-
void getcb_add_return_aioQ(getcb_get2_t *get2cb, val_value_t *val)
Add a return val to a get2cb return_aioQ.
- Parameters:
get2cb -- get2 control block to use
val -- value to add
All In One List Example
In this example the server will invoke GET2 callbacks only for "nonconfig-list" first and then for AIO "nonconfig-list2" list, and will not invoke any deeper level callbacks.
The server will expect that the AIO "nonconfig-list2" callback will fill in the GET2 control block with the sufficient information about "nonconfig-list2" node children, complex children as well as terminal nodes.
Example C files:
Refer to the SIL-SA file
/usr/share/yumapro/src/get3-test/src/get3-test.c
Refer to the SIL file
/usr/share/yumapro/src/get3-test-local/src/get3-test.c
This section illustrates how to use All In One GET2 callbacks for list nodes. Assume we have the same data model as described above then the GET2 callback may look as follow:
/********************************************************************
* FUNCTION get_test_cont_list_list
*
* Get database object callback for list nonconfig-list2
* Path: /get3-nonconfig-cont/nonconfig-list/nonconfig-list2
* Fill in 'get2cb' response fields
*
* sil-aio-get2 extension enabled for this object;
* No callbacks for children will be called
*
* INPUTS:
* see ncx/getcb.h for details (getcb_fn2_t)
*
* RETURNS:
* error status
********************************************************************/
static status_t
get_test_cont_list_list (ses_cb_t *scb,
xml_msg_hdr_t *msg,
getcb_get2_t *get2cb)
{
obj_template_t *obj = GETCB_GET2_OBJ(get2cb);
status_t res = NO_ERR;
/* create 3 top level lists */
uint32 key = 0;
for (key = 1; key < 4; key++) {
const xmlChar *key1 = NULL;
if (key == (uint32)1) {
key1 = (const xmlChar *)"name1";
} else if (key == (uint32)2) {
key1 = (const xmlChar *)"name2";
} else if (key == (uint32)3) {
key1 = (const xmlChar *)"name3";
}
/* make return value that will be added to the return_aioQ */
val_value_t *retval = val_new_value();
if (!retval) {
return ERR_INTERNAL_MEM;
}
val_init_from_template(retval, obj);
/* Use retval for the current callback obj and fill
* it in with folowing terminal and complex nodes;
* then add to get2cb with help of getcb_add_return_aioQ API
*/
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 child node and
* add it to retval of the current callback node
*/
if (!xml_strcmp(name, (const xmlChar *)"name")) {
/* leaf name (string) */
val_value_t *child =
val_make_simval_obj(childobj,
key1,
&res);
if (child) {
res = val_child_add(child, retval);
if (res != NO_ERR) {
val_free_value(child);
val_free_value(retval);
return ERR_NCX_INVALID_VALUE;
}
} else {
val_free_value(retval);
return ERR_NCX_INVALID_VALUE;
}
} else if (!xml_strcmp(name, (const xmlChar *)"nonconfig-list3")) {
/* list nonconfig-list (list) */
uint32 nextlist_key = 0;
for (nextlist_key = 1; nextlist_key < 3; nextlist_key++) {
const xmlChar *keyname = NULL;
if (nextlist_key == (uint32)1) {
keyname = (const xmlChar *)"name1";
} else if (nextlist_key == (uint32)2) {
keyname = (const xmlChar *)"name2";
}
/* create 5 list entries */
val_value_t *listval =
create_list_entry(childobj,
(const xmlChar *)"name",
keyname,
&res);
if (!listval) {
val_free_value(retval);
return ERR_INTERNAL_MEM;
}
/* Use add_force to allow insertion lists with NO keys */
res = val_child_add(listval, retval);
if (res != NO_ERR) {
val_free_value(listval);
val_free_value(retval);
return ERR_NCX_INVALID_VALUE;
}
}
}
}
if (res == NO_ERR) {
/* generate the internal index Q chain */
res = val_gen_index_chain(obj, retval);
if (res != NO_ERR) {
val_free_value(retval);
return res;
}
}
/* Ensure that the generated value is correct and can be added
* to the return Queue.
*/
if (retval && res == NO_ERR) {
res = val_validate_value(retval);
if (res != NO_ERR) {
val_free_value(retval);
return res;
}
}
/* Add created val_value_t to get2cb with help of
* getcb_add_return_aioQ API; In case of list callbacks,
* repeat above steps for another list entry
*/
getcb_add_return_aioQ(get2cb, retval);
}
return res;
} /* get_test_cont_list_list */
Below is the example function that was used in the above AIO callback example:
/********************************************************************
* FUNCTION create_list_entry
*
* Make a list entry based on the key
*
* INPUTS:
* res = return status
*
* RETURNS:
* val_value_t of listval entry if no error
* else NULL
*
*********************************************************************/
static val_value_t *
create_list_entry (obj_template_t *list_obj,
const xmlChar *keyname,
const xmlChar *keystr,
status_t *res)
{
/* find a key child obejct */
obj_template_t *key_obj =
obj_find_child(list_obj,
GET3_TEST_MOD,
keyname);
if (!key_obj) {
*res = ERR_NCX_INVALID_VALUE;
return NULL;
}
val_value_t *list_value = val_new_value();
if (!list_value) {
*res = ERR_NCX_INVALID_VALUE;
return NULL;
}
val_init_from_template(list_value, list_obj);
/* make key leaf entry */
val_value_t *child =
val_make_simval_obj(key_obj,
keystr,
res);
if (!child) {
val_free_value(list_value);
*res = ERR_NCX_INVALID_VALUE;
return NULL;
}
*res = val_child_add(child, list_value);
if (*res != NO_ERR) {
val_free_value(child);
val_free_value(list_value);
return NULL;
}
obj_template_t *notkey_obj =
obj_find_child(list_obj,
GET3_TEST_MOD,
(const xmlChar *)"not-keyname");
if (notkey_obj) {
/* make NON key leaf entry */
child =
val_make_simval_obj(notkey_obj,
(const xmlChar *)"some-value",
res);
if (!child) {
*res = ERR_NCX_INVALID_VALUE;
}
if (*res == NO_ERR) {
*res = val_child_add(child, list_value);
if (*res != NO_ERR) {
val_free_value(child);
}
}
}
obj_template_t *cont_obj =
obj_find_child(list_obj,
GET3_TEST_MOD,
(const xmlChar *)"int-con");
if (cont_obj) {
/* make return value that will be added to the return_aioQ */
val_value_t *contval = val_new_value();
if (!contval) {
val_free_value(list_value);
*res = ERR_NCX_INVALID_VALUE;
return NULL;
}
val_init_from_template(contval, cont_obj);
obj_template_t *leaf_obj =
obj_find_child(cont_obj,
GET3_TEST_MOD,
(const xmlChar *)"con-leaf");
if (leaf_obj) {
val_value_t *leafval =
val_make_simval_obj(leaf_obj,
(const xmlChar *)"42",
res);
if (leafval) {
*res = val_child_add(leafval, contval);
if (*res != NO_ERR) {
val_free_value(leafval);
val_free_value(contval);
}
} else {
val_free_value(contval);
*res = ERR_NCX_INVALID_VALUE;
}
if (*res == NO_ERR) {
*res = val_child_add(contval, list_value);
if (*res != NO_ERR) {
val_free_value(contval);
}
}
} else {
val_free_value(contval);
}
}
/* generate the internal index Q chain */
*res = val_gen_index_chain(list_obj, list_value);
if (*res != NO_ERR) {
log_error("\nError: could not generate index chain (%s)",
get_error_string(*res));
val_free_value(list_value);
return NULL;
}
return list_value;
} /* create_list_entry */
The following section in the AIO callback function example is used to ensure that the constructed value is well-formed and can be safely used by the server.
If the value is not a container or a list or if the list value index chain is malformed this function will return an error "invalid value".
-
status_t val_validate_value(val_value_t *useval)
Verify that the value is valid and can be safely used later Used in the SIL code to ensure that the created value is correct.
Validates that:
value and its OBJ is not NULL
value is list or container
- Parameters:
useval -- val value to check
- Returns:
status
This example shows how this API is used:
/* Ensure that the generated value is correct and can be added
* to the return Queue.
*/
if (retval && res == NO_ERR) {
res = val_validate_value(retval);
if (res != NO_ERR) {
val_free_value(retval);
return res;
}
}
After the data has been created, it must be transferred to the GET2 control block so it can be processed by the server.
/* Add created val_value_t to get2cb with help of
* getcb_add_return_aioQ API; In case of list callbacks,
* repeat above steps for another list entry
*/
getcb_add_return_aioQ(get2cb, retval);
All In One GET2 Callbacks with XML/JSON Buffers
AIO GET2 callbacks follow exactly the same template as regular GET2 callbacks, they use the same registration API and are getting invoked the same way for SIL and SIL-SA as regular GET2 callbacks. The main difference is that there will not be any callback invocations for descendant children and there will not be any callbacks auto generated at all for any of AIO node children.
Note, the AIO callback is not part of the auto-generated code by default and you will have to add an extension to the desired node that you want to become an AIO callback node. Otherwise, the auto generated code will generate regular GET2 callback for that node(s).
AIO XML Callback Example
The AIO GET2 callback may look as follow for the "/get3-config-cont/config-list/nonconfig-list/nonconfig-list2" list. A real callback would probably not have the data hard-wired in strings. This is shown to keep the example simple.
/********************************************************************
* FUNCTION aio_test_get3_config_cont_config_list_nonconfig_list_nonconfig_list2_get
*
* Get database object callback for list nonconfig-list2
* Path: list /get3-config-cont/config-list/nonconfig-list/nonconfig-list2
* Fill in 'get2cb' response fields
*
* sil-aio-get2 extension enabled for this object;
* No callbacks for children will be called
*
* XML encoded buffer is expected
*
* INPUTS:
* see ncx/getcb.h for details (getcb_fn2_t)
*
* RETURNS:
* error status
********************************************************************/
static status_t
aio_test_get3_config_cont_config_list_nonconfig_list_nonconfig_list2_get (
ses_cb_t *scb,
xml_msg_hdr_t *msg,
getcb_get2_t *get2cb)
{
if (LOGDEBUG) {
log_debug("\nEnter aio_test_get3_config_cont_config_list_nonconfig_list_nonconfig_list2_get");
}
(void)scb;
(void)msg;
status_t res = NO_ERR;
/* make XML buffer that will be used instead of return val value
* NOTE if the callback target is a list, the XML buffer must provide
* parent node in the buffer. There cannot be multiple siblings
* in XML, it will make XML malformed.
*
* If the list is a top level node use the following wrapper:
* <config xmlns=\"http://netconfcentral.org/ns/yuma-ncx\"></config>
*/
const xmlChar *xml_buffer = (const xmlChar *)
"<nonconfig-list xmlns=\"http://yumaworks.com/ns/aio-test\">"
"<nonconfig-list2>"
"<name>name1</name><nonconfig-list3>"
"<name>name1</name></nonconfig-list3><nonconfig-list3>"
"<name>name2</name></nonconfig-list3></nonconfig-list2>"
"<nonconfig-list2>"
"<name>name2</name><nonconfig-list3>"
"<name>name1</name></nonconfig-list3><nonconfig-list3>"
"<name>name2</name></nonconfig-list3></nonconfig-list2>"
"<nonconfig-list2>"
"<name>name3</name><nonconfig-list3>"
"<name>name1</name></nonconfig-list3><nonconfig-list3>"
"<name>name2</name></nonconfig-list3></nonconfig-list2>"
"</nonconfig-list>";
/* Add created buffer to return get2cb */
res = getcb_add_return_aio_buff(get2cb, xml_buffer);
return res;
} /* aio_test_get3_config_cont_config_list_nonconfig_list_nonconfig_list2_get */
Row Caching With Key Maps
Note
This feature is available starting in 22.10T-10
The 'ncx/keymap' module provides support to manage the task of finding the next YANG list entry to return for a GET2 Callback GET or GETNEXT request.
The keymap module is designed to work with the GET2 callback parameters. It can automatically support the following GET2 callback tasks:
GET: exact get with full or partial keys
GETNEXT: find the correct next entry with full keys
fixed keys: a fixed key is not allowed to increment during GETNEXT processing. The key map automatically supports fixed key processing.
more_data: The boolean value is provided for the more_data flag that must be returned from the GET2 callback
return keys: The queue of val_value_t return key leafs is provided that must be returned by the GET2 callback.
Key Map Set
A Key Map Set can be used to manage the ordered set of list instances for a YANG list. This can get quite complicated if there is more than one list key leaf.
A Key Map is used to store the ordered list entries.
For system-ordered lists (ordered-by system), the entries are stored in string sorted order.
The key values provided in GETNEXT requests do not have to be exact.
The next entry will be found even if the specified current entry does not exist.
For user-ordered lists (ordered-by user), the entries are stored in the order they are created.
Only insert last mode is supported at this time, since user-ordered operational data is very rarely used.
The key values provided in GETNEXT requests have to be exact.
The next entry will not be found if the specified current entry does not exist.
The Key Map Set is represented by the C data structure 'keymap_set_t' defined in 'ncx/keymap.h'.
Each YANG list GET2 callback needs its own keymap set
Each keymap set has one or more key maps.
Each map set is specific to a subset (or all) of the ancestor key values for the list object.
Rows can be added and deleted from each key map.
A cookie can be stored with each map or row
The typedef for the key map set is accessible but it should not be used directly.
-
struct keymap_set_t
one set of keymaps contains all row entries for one list object Used by SIL or SIL-SA code to cache list entries or just keys so GET2 return key processing can be automated
Public Members
-
obj_template_t *obj
the list object that this keymap is for
-
uint32 anckey_cnt
the number of ancestor keys expected for this list.
Each keymap_map_t must register with this number of ancestor key values
-
dlq_hdr_t cbQ
Queue of keymap_map_t struct Each one represents one keymap for one set of ancestor keys.
-
obj_template_t *obj
Creating a Key Map Set
The top-level key map set must be created in order for a list object to use a key map in the GET2 callback for the list object.
Key Map Set Creation requires the list object pointer and the number of ancestor keys for the list.
Creation can be done at anytime. It is usually done in the SIL or SIL-SA initialization function after the module is loaded.
-
keymap_set_t *keymap_new_set(obj_template_t *listobj, uint32 anckey_cnt, status_t *res)
create a new keymap set
Must be freed with keymap_free_set
- Parameters:
listobj -- object to use for the key map
anckey_cnt -- number of ancestor keys for this keymap set
res -- [out] return status
ERR_INTERNAL_MEM there is not enough memory
ERR_NCX_INVALID_VALUE the object is not a list or no keys
ERR_NCX_MISSING_PARM if listobj parameter is missing
- Returns:
malloced keymap set for the specified object
NULL if *res == some error
Must call 'keymap_free_set' if not-NULL value returned
Example
The key map set is usually created during the "init1" or "init2" phase during server startup. Refer to the Complete Key Map Example for more details.
status_t res = NO_ERR;
test8A_mapset = keymap_new_set(obj, 0, &res);
if (test8A_mapset == NULL) {
return res;
}
Deleting a Key Map Set
The top-level key map set must be freed after is has been created.
Deletion can be done at any time. It is usually done in the SIL or SIL-SA cleanup function.
-
void keymap_free_set(keymap_set_t *mapset)
free a keymap set
The function will simply return if 'mapset' is NULL.
See also
- Parameters:
mapset -- keymap set to free
Example
The key map set is usually deleted during the "cleanup" phase during server shutdown or restart. Refer to the Complete Key Map Example for more details.
keymap_free_set(test8A_mapset);
keymap_free_set(test8B_mapset);
Key Map
The key map is used by the GET2 callback code to store cached list entries. The keys are stored as strings within internal data structures in the key map.
There can be one map for each set of ancestor key values
The number of variable parameters must match the number of ancestor keys for the list. This can be zero.
Variable parameters (...) are used to support any number of list keys with the same functions.
Wildcards are allowed for each ancestor key
The key will match any value used for that ancestor key
A NULL pointer is used for the ancestor key value to create a wildcard and match all values for that key.
The typedef for the key map is accessible but it should not be used directly.
-
struct keymap_map_t
one keymap contains all row entries for one list object Used by SIL or SIL-SA code to cache list entries or just keys so GET2 return key processing can be automated
Public Members
-
dlq_hdr_t qhdr
the queue header; TBD: support AVL tree for big CB
-
keymap_set_t *mapset
the parent keymap set that this keymap is for
-
void *cookie
the cookie passed to the keymap_new_map function use KEYMAP_MAP_COOKIE macro to access this field
-
dlq_hdr_t anckeyQ
Queue of ncx_backptr_t to a malloced string Each one represents the ancestor key value to match.
-
dlq_hdr_t keyQ
Queue of keymap_key_t struct Each one represents the entire subtree for the specific major index in the key_t.
-
dlq_hdr_t qhdr
Creating a Key Map
Key Map Creation requires the Key Map Set pointer and the ancestor key filters for the list.
Creation can be done at anytime. It is usually done in the SIL or SIL-SA initialization function after the module is loaded.
Entries may be required to be added and deleted at runtime as new ancestor key values are used.
The optional cookie is accessed with the KEYMAP_MAP_COOKIE macro
Key maps can be cached or retrieved by the GET2 callback function. The keymap_get_map function is used to retrieve a key map from the set.
-
keymap_map_t *keymap_new_map(keymap_set_t *mapset, void *cookie, status_t *res, ...)
create a new keymap
Must be freed with keymap_free_map
Each variable parameter represents an ancestor key. To match all entries for a specific ancestor key, provide a NULL pointer. Otherwise provide a pointer to a string. This is the exact value that much match the (converted) string value of the ancestor key.
- Parameters:
mapset -- object to use for the key map
cookie -- pointer to save with the keymap
use KEYMAP_MAP_COOKIE macro to access this field
res -- [out] return status
ERR_INTERNAL_MEM there is not enough memory
ERR_NCX_INVALID_VALUE the object is not a list or no keys
ERR_NCX_MISSING_PARM if listobj parameter is missing
- Returns:
malloced keymap for the specified object
NULL if *res == some error
Example
The key map is usually created during runtime or intiialization phase. It is up to the implementation to decide when a new row is needed, and the ancestor key values (if any), or if wildcard ancestor keys should be used.
In this simple example, all the "test8B" inner list entries are the same for all parent list key values. In an actual implementation it is expected that each ancestor key value will have its own key map.
Refer to the Complete Key Map Example for more details.
keymap_map_t *keymap =
keymap_new_map(test8B_mapset,
NULL, // cookie
&res,
NULL, // anckey 1 == wildcard
NULL); // anckey 2 == wildcard
if (keymap == NULL) {
return res;
}
Deleting a Key Map
The key map may be freed after is has been created. It will be automatically freed when its parent key map set is deleted with the 'keymap_free_set' function.
Deletion can be done at any time. It is usually done in the SIL or SIL-SA cleanup function.
-
void keymap_free_map(keymap_set_t *mapset, keymap_map_t *cb)
free a keymap
- Parameters:
mapset -- top map set containing the CB, NULL if none
cb will be removed from this mapset first if non-NULL
cb -- keymap control block to free
Example
/* if the keymap is not part of a key map set */
keymap_free_map(NULL, keymap);
/* if the keymap is part of a key map set */
keymap_free_map(keymapset, keymap);
Retrieving a Key Map
The key map may be retieved or the pointer cached by the GET2 callback. If multiple key maps are used within a key map set then it is useful to retrieve the keymap as it is needed by the GET2 callback function.
-
keymap_map_t *keymap_get_map(keymap_set_t *mapset, ...)
retrieve an existing keymap from its ancestor keys
The variable parameters represent the ancestor keys to match. If no ancestor keys then retrieve the one keymap allowed
- Parameters:
mapset -- object to use to find the key map
- Returns:
the found map set or NULL if not found
Example
This API is called from the GET2 callback that needs to process a GET or GETNEXT request from the server.
Variable parameters (...) are used to support any number of ancestor list keys with this function.
The GET2 callback will pass the ancestor keys (if any) directly to the keymap_get_map function.
If the map set uses wildcard ancestor keys then it will match the exact keys provided to the GET2 callback.
If the map set uses exact ancestor keys then it must match the exact keys provided to the GET2 callback.
Refer to the Complete Key Map Example for more details.
keymap_map_t *keymap =
keymap_get_map(test8B_mapset, k_my8_A, k_my8_B);
Adding an Entry To A Key Map
The 'keymap_add_row' function is used to add a list entry to a key map. This allows the the keymap_add_return_keys function to find the row when searching for entries.
-
status_t keymap_add_row(keymap_map_t *cb, void *cookie, ...)
create a new row in a keymap
Data added to cb; Must be freed with keymap_free_map Each parameter after 'cookie' is a pointer to an xmlString. There must be the exact number of strings for the list The program may crash if there are not enough strings to match the list definition
- Parameters:
cb -- keymap control block to use
cookie -- == optional cookie to store with the row
- Returns:
return status
ERR_INTERNAL_MEM there is not enough memory
ERR_NCX_INVALID_VALUE the object is not a list or no keys
ERR_NCX_MISSING_PARM if listobj parameter is missing
Example
This API is called at runtime or during server initialization to add a row to a key map.
Each row represents one list entry.
The key leaf values are provides as strings, in order.
Variable parameters (...) are used to support any number of list keys with this function.
Refer to the Complete Key Map Example for more details.
res = keymap_add_row(keymap, cookie, BB_D1, BB_E1);
if (res != NO_ERR) {
return res;
}
Removing an Entry From A Key Map
The 'keymap_remove_row' function is used to remove a list entry from a key map.
-
status_t keymap_remove_row(keymap_map_t *cb, ...)
remove and delete a row in a keymap
Each parameter after 'cb' is a pointer to a string. There must be the exact number of strings for the list The program may crash if there are not enough strings to match the list definition.
- Parameters:
cb -- keymap control block to use
- Returns:
return status
ERR_NCX_NOT_FOUND if entry was not found
Example
This API is called at runtime or during server cleanup to remove a row from a key map.
Each row represents one list entry.
The key leaf values are provides as strings, in order.
Variable parameters (...) are used to support any number of list keys with this function.
(void)keymap_remove_row(keymap, BB_D1, BB_E1);
Retrieving the Return Key Data
The 'keymap_get_return_keys' function is used by the GET2 callback to retrieve the requested entry. The parameters passed to the "user" callback can usually be used directly with this API.
-
status_t keymap_get_return_keys(keymap_map_t *cb, boolean getnext, boolean *islast, void **cookie, dlq_hdr_t *keyQ, ...)
get the return keys for the GET or GETNEXT request
Process the key information for the request and produce a queue of val_value_t structs representing the return keys. Handle the missing and fixed key properties for each key
For each key, there are 2 parameters passed in order
value string : key value or NULL for not present
fixed : TRUE for fixed key or FALSE for increment allowed There must be exactly the number of pairs to match the number of key leafs defined for the list object
- Parameters:
cb -- keymap control block to use
getnext -- TRUE for a GETNEXT request; FALSE for GET
islast -- [out] TRUE if returning the last entry; FALSE otherwise
NULL to skip retrieval of this flag
cookie -- [out] the cookie found for the found row
This is only set if the return value is NO_ERR
Set to NULL to ignore the cookie and not retrieve it
keyQ -- [out] Queue of val_value_t to store the return keys
This queue must be cleaned. e.g., use val_clean_valQ
There may be partial results in this queue if error
For getcb, use GETCB_GET2_RETURN_KEYQ(get2cb)
- Returns:
return status
ERR_INTERNAL_MEM there is not enough memory during processing
ERR_NCX_NO_INSTANCE if no return row found
There are some considerations for the processing of the key leaf parameters:
Used to Process GET callback parameters to produce the return keys for a GET2 callback
Ancestor keys are always present
For each list key there will be 3 values
k_prefix_leafname (k_my8_A) is the value of the leaf only if leafname_present (A_fixed) is true
leafname_fixed: TRUE if the key is a fixed leaf The value is not allowed to change for this key leaf. Only entries matching the supplied value are expected for this leaf. Only interesting if the list has multiple keys defined.
Translate GET2 parms to keymap
If A_present == FALSE then the key string must be NULL
If A_present == TRUE then the key string must be set. The value must be converted to an xmlChar string if it is not already a string.
Suggest using val_make_sprintf_string to convert a value to the proper string
The leafname_fixed parameter is passed directly
Example
This API does all the work of GET and GETNEXT processing.
The exact or first entry is retrieved if 'getnext' = 'false'
The next entry is retrieved if 'getnext' = 'true'
The GETNEXT retrieval all not advance for a key if the 'fixed' flag is 'true' for the preceding key leaf.
If an entry is found, then the key leaf val_value_t structs will be added to the provided queue. This must be freed later with the val_clean_valQ function. This is done automatically for the 'get2cb' data structure.
Refer to the Complete Key Map Example for more details.
boolean islast = FALSE; // used for more_data flag
void *retcookie = NULL; // not used here
/* always set the que parameter to the return key Q */
dlq_hdr_t *que = GETCB_GET2_RETURN_KEYQ(get2cb);
/* could cache the keymap pointer since there
* is only one, but normal mode is to lookup the keymap
*/
keymap_map_t *keymap =
keymap_get_map(test8B_mapset, k_my8_A, k_my8_B);
if (keymap != NULL) {
res = keymap_get_return_keys(keymap,
getnext,
&islast,
&retcookie,
que,
k_my8_D,
D_fixed,
k_my8_E,
E_fixed);
if (res != NO_ERR) {
return res;
}
} else {
return ERR_NCX_OPERATION_FAILED;
}
Dump a Key Map for Debugging
The 'keymap_dump_map' can be used by the SIL or SIL-SA code if the log-level parameter is currently set to 'debug' or higher. This will display all the rows in the internal tree format to manage row completions.
-
void keymap_dump_map(keymap_map_t *cb)
debug dump a keymap
log-level=debug is required or nothing will be printed
- Parameters:
cb -- keymap control block to use
Example
if (LOGDEBUG) {
keymap_dump_map(keymap);
}
This example may produce the following output for list 'test8A':
keymap for list 'test8A' (keycnt:2)
Key A = 'xx'
Key B = 'testA'
Key B = 'testB'
Key A = 'yy'
Key B = 'testA'
Key B = 'testB'
This example may produce the following output for list 'test8B':
keymap for list 'test8B' (keycnt:2)
Key D = 'aa'
Key E = 'test1'
Key E = 'test2'
Key D = 'bb'
Key E = 'test1'
Key E = 'test2'
Complete Key Map Example
This example shows how the keymap APIs can be used in GET2 callbacks.
YANG Module and Example Data
YANG Module
The 'mytest8' module contains a simple nested list of operational data. This module can be found in the Core and Advanced SDK packages in the 'netconf/modules/test/pass' directory.
module: mytest8
+--ro test8
+--ro test8A* [A B]
+--ro A string
+--ro B string
+--ro C? string
+--ro test8B* [D E]
+--ro D string
+--ro E string
+--ro F? string
+--ro root
Example Entries
In this example there will 4 entries in the outer list (test8A):
{ "xx", "testA" }
{ "xx", "testB" }
{ "yy", "testA" }
{ "yy", "testB" }
There will be 4 entries in each inner list (test8B)
{ "aa", "test1" }
{ "aa", "test2" }
{ "bb", "test1" }
{ "bb", "test2" }
These values are kept in constants in the 'u_mytest8.c' file:
/* key leaf values entries for 'test8A' */
#define BB_A1 (const xmlChar *)"xx"
#define BB_A2 (const xmlChar *)"yy"
#define BB_B1 (const xmlChar *)"testA"
#define BB_B2 (const xmlChar *)"testB"
/* key leaf values entries for 'test8B' */
#define BB_D1 (const xmlChar *)"aa"
#define BB_D2 (const xmlChar *)"bb"
#define BB_E1 (const xmlChar *)"test1"
#define BB_E2 (const xmlChar *)"test2"
Static Data
In this example the key map set for the 2 lists are kept in static data. The module is checked in "init1" phase and used in "init2" phase.
/* put your static variables here */
static keymap_set_t *test8A_mapset = NULL;
static keymap_set_t *test8B_mapset = NULL;
static ncx_module_t *mytest8_mod = NULL;
Key Leaf Logging
Key Logging Macro
The 'LOG_KEY' macro is defined in this module for convenience:
#define LOG_KEY(N,K,F) \
log_debug_append("\n key %s: %s %s", \
N,K ? K : NCX_EL_NONE, \
(F) ? "(F)" : "")
This is used in GET2 callbacks to extend the auto-generated log trace:
status_t u_my8_test8A_get (
getcb_get2_t *get2cb,
const xmlChar *k_my8_A,
boolean A_fixed,
boolean A_present,
const xmlChar *k_my8_B,
boolean B_fixed,
boolean B_present)
{
(void)A_present;
(void)B_present;
if (LOGDEBUG) {
log_debug("\nEnter u_my8_test8A_get");
LOG_KEY("A", k_my8_A, A_fixed);
LOG_KEY("B", k_my8_B, B_fixed);
}
// ...
}
This will print the key values that are passed to the
GET2 callback and also print the (F)
string to indicate that
the key leaf is a 'fixed' value and is not supposed to increment.
Enter u_my8_test8A_get
key A: xx
key B: testB (F)
Key Map Setup
The key map set and key map for each list must be setup before the GET2 callback functions can be used.
Initialize the Key Maps
There are two static functions to setup the keymap set for each list.
The 'test8A' outer list has no ancestor keys since it is the top-level list. There are no ancestor keys specified in the 'keymap_new_map' function.
static status_t
setup_keymap_test8A (obj_template_t *obj)
{
if (test8A_mapset != NULL) {
return ERR_INTERNAL_INIT_SEQ;
}
void *cookie = obj;
status_t res = NO_ERR;
test8A_mapset = keymap_new_set(obj, 0, &res);
if (test8A_mapset == NULL) {
return res;
}
keymap_map_t *keymap =
keymap_new_map(test8A_mapset,
NULL, // cookie
&res);
if (keymap == NULL) {
return res;
}
res = keymap_add_row(keymap, cookie, BB_A1, BB_B1);
if (res != NO_ERR) {
return res;
}
res = keymap_add_row(keymap, cookie, BB_A1, BB_B2);
if (res != NO_ERR) {
return res;
}
res = keymap_add_row(keymap, cookie, BB_A2, BB_B1);
if (res != NO_ERR) {
return res;
}
res = keymap_add_row(keymap, cookie, BB_A2, BB_B2);
if (res != NO_ERR) {
return res;
}
if (LOGDEBUG) {
keymap_dump_map(keymap);
}
return NO_ERR;
} /* setup_keymap_test8A */
The 'test8B' inner list has 2 ancestor keys (from list test8A) There are therefore two ancestor keys specified in the 'keymap_new_map' function. In this example there is only one key map for all ancestor key values.
static status_t
setup_keymap_test8B (obj_template_t *obj)
{
status_t res = NO_ERR;
if (test8B_mapset != NULL) {
return ERR_INTERNAL_INIT_SEQ;
}
void *cookie = obj;
test8B_mapset = keymap_new_set(obj, 2, &res);
if (test8B_mapset == NULL) {
return res;
}
keymap_map_t *keymap =
keymap_new_map(test8B_mapset,
NULL, // cookie
&res,
NULL, // anckey 1 == wildcard
NULL); // anckey 2 == wildcard
if (keymap == NULL) {
return res;
}
res = keymap_add_row(keymap, cookie, BB_D1, BB_E1);
if (res != NO_ERR) {
return res;
}
res = keymap_add_row(keymap, cookie, BB_D1, BB_E2);
if (res != NO_ERR) {
return res;
}
res = keymap_add_row(keymap, cookie, BB_D2, BB_E1);
if (res != NO_ERR) {
return res;
}
res = keymap_add_row(keymap, cookie, BB_D2, BB_E2);
if (res != NO_ERR) {
return res;
}
if (LOGDEBUG) {
keymap_dump_map(keymap);
}
return NO_ERR;
} /* setup_keymap_test8B */
Creating the Key Map Set
The 'keymap_new_set' function is used in the 'init2' callback. First the list object templates need to be retrieved.
status_t u_mytest8_init2 (void)
{
status_t res = NO_ERR;
if (LOGDEBUG) {
log_debug("\nEnter u_mytest8_init2");
}
/* put your init2 code here */
obj_template_t *obj =
ncx_find_object(mytest8_mod, y_my8_N_test8);
if (obj == NULL) {
return ERR_NCX_DEF_NOT_FOUND;
}
obj_template_t *chobj =
obj_find_child(obj, NULL, y_my8_N_test8A);
if (chobj == NULL) {
return ERR_NCX_DEF_NOT_FOUND;
}
res = setup_keymap_test8A(chobj);
if (res != NO_ERR) {
return res;
}
obj_template_t *chobj2 =
obj_find_child(chobj, NULL, y_my8_N_test8B);
if (chobj2 == NULL) {
return ERR_NCX_DEF_NOT_FOUND;
}
res = setup_keymap_test8B(chobj2);
if (res != NO_ERR) {
return res;
}
return res;
} /* u_mytest8_init2 */
GET2 Callbacks Using Key Maps
GET2 Callback for the Outer List
The entire GET2 function is shown here for the 'test8A' list:
/*
* @brief Get database object callback for list test8A (getcb_fn2_t)\n
* Path: list /test8/test8A\n
*
* Fill in 'get2cb' response fields.
*
* @param get2cb GET2 control block for the callback.
* @param k_my8_A Local key leaf 'A' in list 'test8A'\n
* Path: /my8:test8/test8A/A
* @param A_fixed TRUE if this key is fixed in a getnext request.
* @param A_present TRUE if this key is present and 'k_my8_A' is valid.\n
* FALSE to get first in a getnext request.
* @param k_my8_B Local key leaf 'B' in list 'test8A'\n
* Path: /my8:test8/test8A/B
* @param B_fixed TRUE if this key is fixed in a getnext request.
* @param B_present TRUE if this key is present and 'k_my8_B' is valid.\n
* FALSE to get first in a getnext request.
* @return return status of the callback.
*/
status_t u_my8_test8A_get (
getcb_get2_t *get2cb,
const xmlChar *k_my8_A,
boolean A_fixed,
boolean A_present,
const xmlChar *k_my8_B,
boolean B_fixed,
boolean B_present)
{
(void)A_present;
(void)B_present;
if (LOGDEBUG) {
log_debug("\nEnter u_my8_test8A_get");
LOG_KEY("A", k_my8_A, A_fixed);
LOG_KEY("B", k_my8_B, B_fixed);
}
boolean getnext = FALSE;
/* check the callback mode type */
getcb_mode_t cbmode = GETCB_GET2_CBMODE(get2cb);
switch (cbmode) {
case GETCB_GET_VALUE:
break;
case GETCB_GETNEXT_VALUE:
getnext = TRUE;
break;
default:
/* USE SET_ERROR FOR PROGRAMMING BUGS ONLY */
return SET_ERROR(ERR_INTERNAL_VAL);
}
/* TBD: GET2 callbacks for any datastore; expect operational */
ncx_nmda_ds_t datastore = GETCB_GET2_DATASTORE(get2cb);
(void)datastore; // REMOVE THIS LINE IF datastore USED
obj_template_t *obj = GETCB_GET2_OBJ(get2cb);
status_t res = NO_ERR;
void *cookie = NULL;
uint32 max_entries = GETCB_GET2_MAX_ENTRIES(get2cb);
(void)max_entries; // REMOVE THIS LINE IF max_entries USED
/* For GET, find the entry that matches the key values
* For GETNEXT, find the entry that matches the next key value
* If the 'present' flag is false then return first key instance
* If the 'fixed' flag is true then no GETNEXT advance for the key
* Create a new return key val_value_t, then getcb_add_return_key
*/
if (test8A_mapset == NULL) {
return ERR_NCX_OPERATION_FAILED;
}
boolean islast = FALSE;
dlq_hdr_t *que = GETCB_GET2_RETURN_KEYQ(get2cb);
/* could cache the keymap pointer since there
* is only one, but normal mode is to lookup the keymap
*/
keymap_map_t *keymap = keymap_get_map(test8A_mapset);
if (keymap != NULL) {
res = keymap_get_return_keys(keymap,
getnext,
&islast,
&cookie,
que,
k_my8_A,
A_fixed,
k_my8_B,
B_fixed);
if (res != NO_ERR) {
return res;
}
} else {
return ERR_NCX_OPERATION_FAILED;
}
/***** ADD RETURN KEYS AND REMOVE THIS COMMENT ****/
if (GETCB_GET2_FIRST_RETURN_KEY(get2cb) == NULL) {
return ERR_NCX_NO_INSTANCE;
}
/* Set origin to the correct enumeration found in ncxtypes.h */
ncx_nmda_origin_t origin = NCX_NMDA_ORIGIN_INTENDED;
GETCB_GET2_ORIGIN(get2cb) = origin;
/* 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;
/* For GETNEXT, set the more_data flag to TRUE */
boolean more_data = FALSE;
/**** SET more_data FLAG ****/
more_data = !islast;
GETCB_GET2_MORE_DATA(get2cb) = more_data;
/* 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);
val_value_t *retval = NULL;
/* Retrieve the value of this terminal node and
* add the NMDA origin with val_set_origin and
* add with getcb_add_return_val */
if (!xml_strcmp(name, y_my8_N_C)) {
/* leaf C (string) */
retval = val_make_simval_obj(childobj,
(const xmlChar *)"first-test-val",
&res);
}
if (retval) {
getcb_add_return_val(get2cb, retval);
}
}
return res;
} /* u_my8_test8A_get */
GET2 Callback for the Inner List
The entire GET2 function is shown here for the 'test8B' list:
/*
* @brief Get database object callback for list test8B (getcb_fn2_t)\n
* Path: list /test8/test8A/test8B\n
*
* Fill in 'get2cb' response fields.
*
* @param get2cb GET2 control block for the callback.
* @param k_my8_A Ancestor key leaf 'A' in list 'test8A'\n
* Path: /my8:test8/test8A/A
* @param k_my8_B Ancestor key leaf 'B' in list 'test8A'\n
* Path: /my8:test8/test8A/B
* @param k_my8_D Local key leaf 'D' in list 'test8B'\n
* Path: /my8:test8/test8A/test8B/D
* @param D_fixed TRUE if this key is fixed in a getnext request.
* @param D_present TRUE if this key is present and 'k_my8_D' is valid.\n
* FALSE to get first in a getnext request.
* @param k_my8_E Local key leaf 'E' in list 'test8B'\n
* Path: /my8:test8/test8A/test8B/E
* @param E_fixed TRUE if this key is fixed in a getnext request.
* @param E_present TRUE if this key is present and 'k_my8_E' is valid.\n
* FALSE to get first in a getnext request.
* @return return status of the callback.
*/
status_t u_my8_test8B_get (
getcb_get2_t *get2cb,
const xmlChar *k_my8_A,
const xmlChar *k_my8_B,
const xmlChar *k_my8_D,
boolean D_fixed,
boolean D_present,
const xmlChar *k_my8_E,
boolean E_fixed,
boolean E_present)
{
(void)D_present;
(void)E_present;
if (LOGDEBUG) {
log_debug("\nEnter u_my8_test8B_get");
LOG_KEY("A", k_my8_A, false);
LOG_KEY("B", k_my8_B, false);
LOG_KEY("D", k_my8_D, D_fixed);
LOG_KEY("E", k_my8_E, E_fixed);
}
boolean getnext = FALSE;
/* check the callback mode type */
getcb_mode_t cbmode = GETCB_GET2_CBMODE(get2cb);
switch (cbmode) {
case GETCB_GET_VALUE:
break;
case GETCB_GETNEXT_VALUE:
getnext = TRUE;
break;
default:
/* USE SET_ERROR FOR PROGRAMMING BUGS ONLY */
return SET_ERROR(ERR_INTERNAL_VAL);
}
/* TBD: GET2 callbacks for any datastore; expect operational */
ncx_nmda_ds_t datastore = GETCB_GET2_DATASTORE(get2cb);
(void)datastore; // REMOVE THIS LINE IF datastore USED
obj_template_t *obj = GETCB_GET2_OBJ(get2cb);
status_t res = NO_ERR;
uint32 max_entries = GETCB_GET2_MAX_ENTRIES(get2cb);
(void)max_entries; // REMOVE THIS LINE IF max_entries USED
if (test8B_mapset == NULL) {
return ERR_NCX_OPERATION_FAILED;
}
boolean islast = FALSE;
void *retcookie = NULL;
dlq_hdr_t *que = GETCB_GET2_RETURN_KEYQ(get2cb);
/* could cache the keymap pointer since there
* is only one, but normal mode is to lookup the keymap
*/
keymap_map_t *keymap =
keymap_get_map(test8B_mapset, k_my8_A, k_my8_B);
if (keymap != NULL) {
res = keymap_get_return_keys(keymap,
getnext,
&islast,
&retcookie,
que,
k_my8_D,
D_fixed,
k_my8_E,
E_fixed);
if (res != NO_ERR) {
return res;
}
} else {
return ERR_NCX_OPERATION_FAILED;
}
/* For GET, find the entry that matches the key values
* For GETNEXT, find the entry that matches the next key value
* If the 'present' flag is false then return first key instance
* If the 'fixed' flag is true then no GETNEXT advance for the key
* Create a new return key val_value_t, then getcb_add_return_key
*/
/***** ADD RETURN KEYS AND REMOVE THIS COMMENT ****/
if (GETCB_GET2_FIRST_RETURN_KEY(get2cb) == NULL) {
return ERR_NCX_NO_INSTANCE;
}
/* Set origin to the correct enumeration found in ncxtypes.h */
ncx_nmda_origin_t origin = NCX_NMDA_ORIGIN_INTENDED;
GETCB_GET2_ORIGIN(get2cb) = origin;
/* 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;
/* For GETNEXT, set the more_data flag to TRUE */
boolean more_data = FALSE;
/**** SET more_data FLAG ****/
more_data = !islast;
GETCB_GET2_MORE_DATA(get2cb) = more_data;
/* 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);
val_value_t *retval = NULL;
/* Retrieve the value of this terminal node and
* add the NMDA origin with val_set_origin and
* add with getcb_add_return_val */
if (!xml_strcmp(name, y_my8_N_F)) {
/* leaf F (string) */
retval = val_make_simval_obj(childobj,
(const xmlChar *)"testval",
&res);
}
if (retval) {
getcb_add_return_val(get2cb, retval);
}
}
return res;
} /* u_my8_test8B_get */
NETCONF <get> Examples
Complete Data Set
The complete data set for the 'test8' container contains 16 list entries (4 outer list 'test8A' * 4 inner list 'test8B') Only one of the inner lists is shown here since all four are the same in this example.
> sget /test8
This will produce the XML reply in this example:
<rpc-reply message-id="10"
xmlns:ncx="http://netconfcentral.org/ns/yuma-ncx"
ncx:last-modified="2023-07-06T21:51:13Z" ncx:etag="7577"
xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<data>
<test8 xmlns="urn:yumaworks:params:xml:ns:yang:mytest8">
<test8A>
<A>xx</A>
<B>testA</B>
<C>first-test-val</C>
<test8B>
<D>aa</D>
<E>test1</E>
<F>testval</F>
<root>
</root>
</test8B>
<test8B>
<D>aa</D>
<E>test2</E>
<F>testval</F>
<root>
</root>
</test8B>
<test8B>
<D>bb</D>
<E>test1</E>
<F>testval</F>
<root>
</root>
</test8B>
<test8B>
<D>bb</D>
<E>test2</E>
<F>testval</F>
<root>
</root>
</test8B>
</test8A>
<test8A>
<A>xx</A>
<B>testB</B>
<C>first-test-val</C>
<!-- repeat 4 inner test8B same as above -->
</test8A>
<test8A>
<A>yy</A>
<B>testA</B>
<C>first-test-val</C>
<!-- repeat 4 inner test8B same as above -->
</test8A>
<test8A>
<A>yy</A>
<B>testB</B>
<C>first-test-val</C>
<!-- repeat 4 inner test8B same as above -->
</test8A>
</test8>
</data>
</rpc-reply>
Select Filter With Fixed Keys
This example sets one outer key and one inner key as fixed key values in the client request.
The fixed value flags are ignored for a GET request
If the flag is set, then the GET2 callback must not increment the key leaf when searching for a GETNEXT value.
Only unfixed key values can change for a GETNEXT request
Any key can be a fixed value
If all keys are fixed values then a GETNEXT request will return
ERR_NCX_NO_INSTANCE
because no key leafs are allowed to increment.If all keys are fixed values for a GET request then only that exact instance is requested.
> sget /test8/test8A[A='yy']/test8B[E='test2']
This will produce the XML reply in this example:
<rpc-reply message-id="11"
xmlns:ncx="http://netconfcentral.org/ns/yuma-ncx"
ncx:last-modified="2023-07-06T21:51:13Z" ncx:etag="7577"
xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<data>
<test8 xmlns="urn:yumaworks:params:xml:ns:yang:mytest8">
<test8A>
<A>yy</A>
<B>testA</B>
<test8B>
<D>aa</D>
<E>test2</E>
<F>testval</F>
<root>
</root>
</test8B>
<test8B>
<D>bb</D>
<E>test2</E>
<F>testval</F>
<root>
</root>
</test8B>
</test8A>
<test8A>
<A>yy</A>
<B>testB</B>
<test8B>
<D>aa</D>
<E>test2</E>
<F>testval</F>
<root>
</root>
</test8B>
<test8B>
<D>bb</D>
<E>test2</E>
<F>testval</F>
<root>
</root>
</test8B>
</test8A>
</test8>
</data>
</rpc-reply>
Select Filter For One List Entry
This example sets both outer keys and both inner keys as fixed key values in the client request. Only one entry is expected in the result.
> sget /test8/test8A[A='yy'][B='testA']/test8B[D='aa'][E='test2']
This will produce the XML reply in this example:
<rpc-reply message-id="13"
xmlns:ncx="http://netconfcentral.org/ns/yuma-ncx"
ncx:last-modified="2023-07-06T21:51:13Z" ncx:etag="7577"
xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<data>
<test8 xmlns="urn:yumaworks:params:xml:ns:yang:mytest8">
<test8A>
<A>yy</A>
<B>testA</B>
<test8B>
<D>aa</D>
<E>test2</E>
<F>testval</F>
<root>
</root>
</test8B>
</test8A>
</test8>
</data>
</rpc-reply>
Select Filter For a Leaf Within a Choice
In this scenario, the selection node is a leaf, and the server will pre-process the node to determine whether it should write the parent node. If no matching selection nodes are found, the parent node will be skipped entirely, as neither the content match, nor the selection nodes matched the criteria.
Note
- The server will invoke the choice GET2 callback twice in this process:
First, to pre-process the selection node.
Second, to output the selected node.
To properly handle the pre-processing step, a GET2 Control Block state flag can be used to indicate when the callback is in pre-process mode.
Example of Handling the Pre-Process Flag:
if (GETCB_GET2_PRE_PROCESS(get2cb)) {
log_debug("\nPre-process mode");
} else {
log_debug("\nRegular GET2 callback mode");
count++;
}
Example of Retrieving a Leaf within a Choice:
> sget /top-cont/inner-cont/leaf-in-choice
This command will return an XML reply, as demonstrated below.
<rpc-reply message-id="14"
xmlns:ncx="http://netconfcentral.org/ns/yuma-ncx"
ncx:last-modified="2024-09-13T17:50:51Z" ncx:etag="860349"
xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<data>
<top-cont xmlns="urn:yumaworks:params:xml:ns:yang:mytest">
<inner-cont>
<leaf-in-choice>4</leaf-in-choice>
</inner-cont>
</top-cont>
</data>
</rpc-reply>