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
message 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.
-
dlq_hdr_t qhdr
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",
¬obj);
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 activeAGT_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 */