Notifications

The yangdump-pro program will automatically generate functions to queue a specific notification type for processing. It is up to the SIL callback code to invoke this function when the notification event needs to be generated. The SIL code is expected to provide the value nodes that are needed for any notification payload objects. SIL-SA uses a different function to queue a notification than SIL code, but all other APIs are the same.

The Notification Send function is not a callback. Its purpose is to generate a specific event, but the same function could be used for multiple event types. If the notification-stmt for the event type defines child nodes then the send function is expected to provide this data (in val_value_t nodes) when the notification is queued.

SIL or SIL-SA code can include an Event Stream Callback function to know if any clients are listening on the relevant event stream. If no subscriptions are active, then the notification send processing logic can decide to suppress generation of certain events.

Notification Structure

The agt_not_msg_t structure is used to convey the event and parameters to the server for processing. It should not be accessed directly. There are APIs for all notification message interactions between SIL or SIL-SA code and the server. The agt/agt_not.h file contains all the API functions and the message structure.

struct agt_not_msg_t

one notification message that will be sent to all subscriptions in the stream and kept in the replay buffer for that stream (notificationQ)

Public Members

dlq_hdr_t qhdr

queue header

obj_template_t *notobj

notification event object for this message

dlq_hdr_t keyQ

notification ancestor keyQ if this is a YANG 1.1 nested notification.

  • In this case the notobj is not a top-level object and the value tree will be generated with the notification within it, as defined in RFC 7950.

  • Keys set in order by the caller: Q of val_value_t

  • These value structs can be generic

  • The names are not important and will not be used

  • Only the order is important: top-down, left-to-right

  • The actual object template for each key leaf will be used when the notification message is generated

dlq_hdr_t payloadQ
uint32 msgid

messsage ID assigned to this notification

xmlChar eventTime[TSTAMP_MIN_SIZE]

event received timestamp

val_value_t *msg

the /notification element

  • This is malloced and freed when this struct is freed

val_value_t *event

backptr inside msg for filter for top-level notifications

boolean usemsgid

internal field to use or change msgid

status_t res

internal status for making the message to prevent a malformed notification message from being sent to subsequent sessions

val_value_t *treetop

internal backptr to the top of the instance hierarchy if this is a nested notification; YANG 1.1 says to use this treetop for the filter Not the event embedded within it

ncx_sm_mpid_t *sm_mpid

clone of the MPID to use if this is a schema-mounted notification.

If not set then the first MPID will be used. If no MP instances found then the notification will be dropped.

The first step for a SIL or SIL-SA "send" function is to allocate a new notification message:

agt_not_msg_t *agt_not_new_notification(obj_template_t *eventType)

Malloc and initialize the fields in an agt_not_msg_t.

Parameters:

eventType -- object template of the event type

Returns:

pointer to the malloced and initialized struct or NULL if an error

Normally this structure is queued for delivery. Use the following function to free a notification message instead of queing it for delivery.

void agt_not_free_notification(agt_not_msg_t *notif)

Scrub the memory in an agt_not_template_t by freeing all the sub-fields and then freeing the entire struct itself.

The struct must be removed from any queue it is in before this function is called.

Parameters:

notif -- agt_not_template_t to delete

APIs for Nested Notifications

Note

Nested notifications are only available in the 22.10 (and later) release trains.

YANG 1.1 allows notifications to be nested within data nodes, in addition to the normal top-level notifications. These notifications are supported starting in release 21.10T-7. Refer to the YANG 1.1 Nested Notifications section for examples of nested notifications.

Add Ancestor Key APIs

The SIL or SIL-SA "send" function must provide any key leaf values for the ancestor list nodes that contain the notification.

The following API is used to add a key leaf to the notification message:

void agt_not_add_key(agt_not_msg_t *notif, val_value_t *val)

Queue the specified value node into the keyQ for the specified notification.

This is the only API used by SIL or SIL-SA code to add the ancestor key nodes for a nested notification

val will added to the notif->keyQ

Parameters:
  • notif -- notification to send

  • val -- value to add to keyQ

The auto-generated SIL or SIL-SA code for the "send" function does all the required work to properly send the API. These APIs are optional to use so any custom "send" function must following the same API steps.

The agt/agt_util.h file contains API functions to generate a string based key value, even for other data types. They are used in the auto-generated code for a "send" function.

Note

All required ancestor key leaf values must be provided or the notification will be dropped and not queued for delivery

  • The base type of each leaf value must be NCX_BT_STRING

  • The key leafs must be queued in the correct order

val_value_t *agt_make_string_key(const xmlChar *leafval)

make a generic key leaf node from a string

For use with the agt_not_add_key API only!

Parameters:

leafval -- string version of value to set for leaf

Returns:

malloced value node

val_value_t *agt_make_uint_key(uint32 leafval)

make a generic key leaf node from a uint

For use with the agt_not_add_key API only!

Parameters:

leafval -- uint value to set for leaf

Returns:

malloced value node

val_value_t *agt_make_int_key(int32 leafval)

make a generic key leaf node from an int

For use with the agt_not_add_key API only!

Parameters:

leafval -- int value to set for leaf

Returns:

malloced value node

val_value_t *agt_make_uint64_key(uint64 leafval)

make a generic key leaf node from a uint64

For use with the agt_not_add_key API only!

Parameters:

leafval -- uint64 value to set for leaf

Returns:

malloced value node

val_value_t *agt_make_int64_key(int64 leafval)

make a generic key leaf node from an int64

For use with the agt_not_add_key API only!

Parameters:

leafval -- int64 value to set for leaf

Returns:

malloced value node

val_value_t *agt_make_boolean_key(boolean leafval)

make a generic key leaf node from a boolean

For use with the agt_not_add_key API only!

Parameters:

leafval -- boolean value to set for leaf

Returns:

malloced value node

val_value_t *agt_make_idref_key(const val_idref_t *leafval)

make a generic key leaf node from an idref struct

For use with the agt_not_add_key API only!

Parameters:

leafval -- idref value to set for leaf

Returns:

malloced value node

val_value_t *agt_make_bits_key(const ncx_list_t *leafval)

make a generic key leaf node from a bits struct

For use with the agt_not_add_key API only!

Parameters:

leafval -- bits value to set for leaf

Returns:

malloced value node

Example Send Function For Nested Notification

This autogenerated code is for one of the example notifications in the YANG 1.1 Nested Notifications section.

/**
 * @brief Send a "<N1>" notification.
 * Path: /A/B/BB/N1
 *
 * Called by your code when notification event occurs.
 *
 * This API is optional. It can be replaced with
 * any function of your choice that provides the same functionality.
 *
 * Create an internal notification message (agt_not_msg_t) and queue
 * it for delivery to client sessions.
 *
 * The parameters depend in the data definitions within the
 * notification-stmt for this event type.
 *
 * @param k_n4_row Ancestor key leaf 'row' in list 'B'\n
 * Path: /n4:A/n4:B/n4:row
 * @param k_n4_column Ancestor key leaf 'column' in list 'B'\n
 * Path: /n4:A/n4:B/n4:column
 * @param k_n4_idx Ancestor key leaf 'idx' in list 'BB'\n
 * Path: /n4:A/n4:B/n4:BB/n4:idx
 */
void u_n4_N1_1_send (
    uint16 k_n4_row,
    uint16 k_n4_column,
    const xmlChar *k_n4_idx,
    const xmlChar *v_parm3,
    int16 v_parm4)
{
    val_value_t *parmval = NULL;
    status_t res = NO_ERR;

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

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

    obj_template_t *notobj = NULL;
    res = xpath_find_schema_target_int(
        (const xmlChar *)"/n4:A/n4:B/n4:BB/n4:N1",
        &notobj);
    if (res != NO_ERR) {
        log_error("\nError: cannot find notification object");
        return;
    }

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

    /* add /A/B/row key to notification */
    parmval = agt_make_uint_key(k_n4_row);
    if (parmval) {
        agt_not_add_key(notif, parmval);
    } else {
        log_error("\nError: Cannot add keyval to notification");
    }

    /* add /A/B/column key to notification */
    parmval = agt_make_uint_key(k_n4_column);
    if (parmval) {
        agt_not_add_key(notif, parmval);
    } else {
        log_error("\nError: Cannot add keyval to notification");
    }

    /* add /A/B/BB/idx key to notification */
    parmval = agt_make_string_key(k_n4_idx);
    if (parmval) {
        agt_not_add_key(notif, parmval);
    } else {
        log_error("\nError: Cannot add keyval to notification");
    }

    /* add parm3 to payload */
    parmval = agt_make_leaf2(
        notobj,
        y_n4_M_n4,
        y_n4_N_parm3,
        v_parm3,
        &res);
    if (parmval == NULL) {
        log_error("\nError: Cannot add leaf 'parm3' to payload (%s)",
            get_error_string(res));
    } else {
        agt_not_add_to_payload(notif, parmval);
    }

    /* add parm4 to payload */
    parmval = agt_make_int_leaf2(
        notobj,
        y_n4_M_n4,
        y_n4_N_parm4,
        v_parm4,
        &res);
    if (parmval == NULL) {
        log_error("\nError: Cannot add leaf 'parm4' to payload (%s)",
            get_error_string(res));
    } else {
        agt_not_add_to_payload(notif, parmval);
    }

    agt_not_queue_notification(notif);

} /* u_n4_N1_1_send */

Notification Send Function Variants

There are 4 API functions for sending notifications:

Send notification for SIL

Send to the default or configured event stream for SIL. This is the recommended function to use.

void agt_not_queue_notification(agt_not_msg_t *notif)

Queue the specified notification in the replay log.

It will be sent to all the active subscriptions as needed.

message added to the notificationQ

Parameters:

notif -- notification to send

Send notification for SIL-SA

Send to the default or configured event stream for SIL-SA.

void sil_sa_queue_notification(agt_not_msg_t *notif)

Send a notification-event to the main server for queing by the agt_not module.

Parameters:

notif --

notification struct to send

Will be consumed and freed even if there is an error

Send notification for SIL to a Specific Event Stream

Send a notification event to a specific event stream for SIL. This will override any operator-configured event streams. Intended for use with hard-wired vendor event stream names.

void agt_not_queue_notification_stream(const xmlChar *stream_name, agt_not_msg_t *notif)

Queue the specified notification in the replay log.

It will be sent to all the active subscriptions as needed.

This API should not be used if the module to event stream mapping configuration is used instead.

Use this API if the event-stream configuration is hard-wired by the vendor. The module ot event stream mappings will be ignored if this API is ued

message added to the notificationQ

Parameters:
  • stream_name -- stream name to use (NULL use NETCONF)

  • notif -- notification to send

Send notification for SIL-SA to a Specific Event Stream

Send notification to custom event stream for SIL-SA. This will override any operator-configured event streams. Intended for use with hard-wired vendor event stream names.

void sil_sa_queue_notification_stream(const xmlChar *stream_name, agt_not_msg_t *notif)

Send a notification-event to a specific stream, to the main server for queing by the agt_not module.

Use this API for hard-wired event streams only! If the module-to-stream mappings are used then DO NOT use this API because the mappings will be ignored.

Parameters:
  • stream_name -- optional stream name (NULL = use NETCONF)

  • notif --

    notification struct to send

    Will be consumed and freed even if there is an error

Notification Send Function Example

The function to generate a notification control block and queue it for notification replay and delivery is generated by the yangdump-pro program. A function parameter will exist for each top-level data node defined in the YANG notification definition.

This function is not called by the server directly so it does not have a function typedef. It can be changed or not used at all to generate a notification event.

In the example below, the 'toastDone' notification event contains just one leaf, called the 'toastStatus'. There is SIL timer callback code which calls this function, and provides the final toast status, after the <make-toast> operation has been completed or canceled.

/********************************************************************
* FUNCTION y_toaster_toastDone_send
*
* Send a y_toaster_toastDone notification
* Called by your code when notification event occurs
*
********************************************************************/
void
    y_toaster_toastDone_send (
        const xmlChar *toastStatus)
{
    agt_not_msg_t *notif;
    val_value_t *parmval;
    status_t res;

    res = NO_ERR;

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

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

    /* add toastStatus to payload */
    parmval = agt_make_leaf(
        toastDone_obj,
        y_toaster_N_toastStatus,
        toastStatus,
        &res);
    if (parmval == NULL) {
        log_error(
            "\nError: make leaf failed (%s), cannot send <toastDone> notification",
            get_error_string(res));
    } else {
        agt_not_add_to_payload(notif, parmval);
    }

    agt_not_queue_notification(notif);

    /* !!!  Note that for SIL-SA code the following line is
     * !!!  generated instead of agt_not_queue_notification
     * !! ! sil_sa_queue_notification(notif);
     */

} /* y_toaster_toastDone_send */

Event Stream Callbacks

An event stream callback is used by SIL or SIL-SA code to monitor event stream state changes. This allows the server to be more efficient by reducing CPU and memory resources used to generate an event when no client is listening.

Note that the "replay" feature of NETCONF notifications will not be available either if the Event Stream Callback is used to suppress event generation.

There are currently 2 state changes (called sub-events) defined for an event stream

  • active: the event stream has one or more client subscriptions

  • inactive: the event stream has zero client subscriptions

After the event stream callback is registered, it will be invoked right away to establish the initial state of the event stream.

After that, the callback will only be invoked when the event stream state is changing.

The 'agt_not_stream_cbfn_t' function in agt/agt_not.h is used as the callback template for the event stream callback.

typedef void (*agt_not_stream_cbfn_t)(const xmlChar *event_stream, agt_not_subevent_t subevent, void *cookie)

Event Stream Callback.

Used by SIL code to determine if events should be generated or not. The server will invoke the callback with an event stream sub-event type.

  • active : the event-stream is now active. There is a client subscribing to this event stream.

  • inactive : the event-stream is now inactive; There are no clients subscribing to this event stream

It is possible that subscription filters prevent specific events from reaching the subscriber. These corner-cases are ignored for this callback. Only subscriptions are tracked, not the actual notifications delivered on each subscription.

There will be one callback per event-stream so if multiple event streams are modified at once then these callbacks will be serialized, one per event-stream.

It is possible a callback will receive a redundant event, e.g., two 'active' events in a row. This is normal as the server does not maintain an event history for each callback, This can happen if the yumaworks-event-stream module is used to edit the stream configuration.

Note that the replay-buffer is not considered when determining if an event-stream is active or not. The replay feature is rarely used. This callback is used to determine if active client sessions are subscribed to the event-stream. The notification generator SHOULD NOT use this callback if the replay buffer is considered make the event-stream be active.

The callbacks done will depend on the registration parameters. For the modname parameter:

  • this parameter is mandatory. The callback must be associated with a real module, even if that module is never removed.

  • normal SIL mode and only SIL-SA mode will set the module name that contains the notifications being generated.

  • The server will use this module name to track the event stream that the module is mapped into at the moment.

  • If the yumaworks-event-stream module is used and the module mapping changes, then the new event stream will be used. For the stream_name parameter:

  • If used then only events for this stream will be done

  • The module name will be ignored if event stream is set.

  • This mode is intended to support the vendor hard-wired event-stream configuration, and the SIL code uses the agt_not_queue_notification_stream API

Param event_stream:

event-stream name the event is for.

  • Normally the callback can ignore this parameter.

  • If the callback is registering for all event streams instead of one, then this parameter may be useful.

Param subevent:

sub-event type being reported.

  • AGT_NOT_SUBEV_ACTIVE : stream is active

  • AGT_NOT_SUBEV_INACTIVE : stream is inactive

Param cookie:

the cookie passed to the registration function

Event Stream Callback Initialization and Cleanup

The 'agt_not_register_stream_cbfn' function in agt/agt_not.h is used to provide an event stream callback.

Multiple modules can use the same callback function.

The registration is done during the Initialization Phase 1 before the startup configuration is loaded into the running configuration database.

status_t agt_not_register_stream_cbfn(const xmlChar *modname, const xmlChar *stream_name, boolean all_streams, agt_not_stream_cbfn_t cbfn, void *cookie)

Register a SIL event-stream callback function.

Used to help suppress event generation if nobody is listening

YPW-1816. For SIL code, there will be multiple callback registrations per module supported.

For SIL-SA code, there will be one callback registration per module supported. Only only registration for each value of the modname parameter will be allowed.

A callback can choose 1 of 3 operational modes:

  • Mode 1: Module mapping

    Use the module to event stream mapping to determine the event stream of interest.

  • Mode 2: Hard-wired event-stream

    Use the provided event stream name to determine the event stream of interest.

  • Mode 3: All event streams

    Receive callbacks for all event streams.

Parameters:
  • modname -- module name that this callback is registering for.

    • The normal mode of operation is to set this to the module name that contains the notif 'send' functions.

    • The server will figure out which event-stream is being used for events from this module.

    • This parameter must be present, even if the stream_name or all_streams flag is present. It identifies the module SIL or SIL-SA code that is associated with this callback, if the module is removed.

  • stream_name -- event stream name to register for.

    • Normal mode of operation is set to NULL because the event-stream configuration is not known to the SIL code.

    • If set, then only register for this event-stream

    • If NULL and modname is NULL then register for all events for all event-streams.

  • all_streams --

    TRUE if this callback is registering for all event streams.

    FALSE for normal mode and registering for 1 module name of one event stream.

  • cbfn -- callback function to use

  • cookie -- pointer to pass to the callback function as a cookie

Returns:

status;

  • It is not an error to register for a non-existent module name or event stream name since they can be added and removed at run-time. Make sure parameters are spelled correctly!

  • The agt_not_unregister_event_stream_cbfn API MUST be called before the module containing the callback is removed.

  • It is not an error to register duplicates. These will not be checked.

In this example using t54.yang, add to the registration call to the 'u_t54_init' function:

 need_t54_notifs = TRUE;

 status_t res =
     agt_not_register_stream_cbfn(ncx_get_modname(t54_mod),
                                  NULL,  // hard-wired event stream
                                  FALSE,   // all_streams
                                  t54_stream_callback);

If a callback is registered, it must be unregistered during the cleanup phase.

void agt_not_unregister_stream_cbfn(const xmlChar *modname, agt_not_stream_cbfn_t cbfn)

Unregister a SIL event-stream callback function.

YPW-1816.

Parameters:
  • modname -- module name used in the register request

  • cbfn -- callback function to unregister.

    • All entries with the same modname using this callback will be removed.

    • No errors are generated if the callback is not found

Event Stream Callback Example

In this example, the “t54” SIL code maintains a boolean flag to track the event stream state. If the event stream is inactive, then the “send” function will skip sending and drop the event instead.

Static Data

The module needs to maintain a boolean variable that is initialized to TRUE.

The module pointer for the “t54” YANG module is also stored.

/* put your static variables here */
static boolean need_t54_notifs;
static ncx_module_t *t54_mod;

Event Stream Callback

In this example, the boolean flag 'need_t54_notifs' is updated.

static void t54_stream_callback (const xmlChar *event_stream,
                                 agt_not_subevent_t subevent,
                                 void *cookie)
{
    (void)cookie;

    if (LOGDEBUG) {
        log_debug("\nGot Event Stream Callback for '%s'",
                  event_stream ? event_stream : NCX_EL_NONE);
    }

    if (subevent == AGT_NOT_SUBEV_ACTIVE) {
        if (LOGDEBUG) {
            log_debug_append(" active");
        }
        need_t54_notifs = TRUE;
    } else if (subevent == AGT_NOT_SUBEV_INACTIVE) {
        if (LOGDEBUG) {
            log_debug_append(" inactive");
        }
        need_t54_notifs = FALSE;
    }
}

Modified Notification Send Function

The notification send functions generated for the t54 module needs to be updated to check the boolean flag 'need_t54_notifs'.

 void u_t_t54_test1_send (
     const xmlChar *v_name)
 {
     val_value_t *parmval = NULL;
     status_t res = NO_ERR;

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

     if (!need_t54_notifs) {
         log_debug2("\nSkipping <t54-test1> notification; stream not active");
         return;
     }

     if (LOGDEBUG) {
         log_debug("\nGenerating <t54-test1> notification");
     }

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

     /* add name to payload */
     parmval = agt_make_leaf2(
         t54_test1_obj,
         y_t_M_t,
         y_t_N_name,
         v_name,
         &res);
     if (parmval == NULL) {
         log_error("\nError: Cannot add leaf 'name' to payload (%s)",
             get_error_string(res));
     } else {
         agt_not_add_to_payload(notif, parmval);
     }

     agt_not_queue_notification(notif);

 } /* u_t_t54_test1_send */