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:

  1. object template based “GET2” callback

  2. value node based static operational data

  3. value node based "GET1" virtual operational data

GET1 Callback

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:

  1. value node based virtual data (GET1 callback)

  2. value node based static data (GET1 callback)

  3. 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.

Which Nodes Have GET2 Callbacks

Complex child nodes are expected to have their own GET2 callbacks registered. The GET2 callbacks are registered with an API similar to the EDIT callback registration.

  • The following 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.

  • The server will expect configuration data nodes to be present in the target datastore.

  • If the retrieval request includes config=false nodes, then the server will check each child 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 &#8212; 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

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-exact

      • GETCB_GETNEXT_VALUE for get-next

      • GETCB_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

    • 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, END

      • GETCB_API_MODE_1SHOT: entry called with 1SHOT, no child nodes walked

      • GETCB_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 returned

  • ERR_NCX_NO_INSTANCE: the specified data instance does not exist

  • Other 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

anyxml

Returns the entire anyxml value tree

leaf

Returns the leaf value

leaf-list

Returns all values for the leaf-list

choice

Returns the name of the active case and child terminal nodes in the active case

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:

  1. Add a static variable for the index

/* put your static variables here */
static uint32 user_data_index = 0;
  1. Initialize the data in the 'u_m3_init' function

/* put your module initialization code here */
user_data_index = 0;
  1. 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));

    }

    // ...
}
  1. 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

agt_make_leaf

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 &#8212; 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 &#8212; 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 filed 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.

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:

  1. ancestor keys: these key values are always set and always fixed

  2. local keys: these key values are for the target list object.

    1. key value: The value of they key in its base type; Only valid if foo_present flag is true

    2. foo_present: true if the foo local key leaf value is valid

    3. 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:

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 Once 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.

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

keymap_new_set

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.

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>