Schema Mount Server APIs
The Schema Mount feature allows entire YANG modules to be mounted within another module, similar to augments, but much more powerful and flexible than augments.
This feature is simple to understand but requires significant complexity to implement in the YANG compiler and the server automation.
First Available: 23.10
Schema Mount Aware Server Code
There are several design considerations for deploying complex Schema Mount YANG module instrumentation.
A schema mount "location" is either the top-level or a specific mount-point root supported by the server.
Note
Each location has its own modules, revisions, features, deviations, and annotations.
There must be SIL callbacks installed for all ancestors of any mounted modules in order for mounted SIL callbacks to be invoked.
Schema Mount Design Principles
The "root" data node of a YANG datastore is used to represent the entire configuration or operational state of a server. There are no actual child nodes defined. Instead, all top-level YANG data nodes (from the YANG modules loaded into the server) are available as child nodes of the root.
The Schema Mount feature allows a configuration container or list node to be defined as a mount point, and used like the root node. There are no actual child nodes (except list key leafs) defined in a mount point. Instead, all top-level YANG data nodes (from the YANG modules specified in the sm-config Structure are available as child nodes of the root. There may be many instances of the mount point, since it (or an ancestor) can be a YANG list.
The design for SIL and SIL-SA code for mounted modules does not need to change. The code is generated and written as if it is located at the top-level. The Schema Mount framework allows YANG modules and related source code to be reused. The server APIs have been enhanced to no SIL or SIL-SA code changes are needed to implement a YANG module within a mount point instead of the top-level.
A Schema Mount Aware library needs to know where it is mounted. It is very simple to convert existing code (or create new code) to utilize the mount point instance (MPID) information. Refer to the Tutorial: Schema Mounted Virtual Toaster section for an example.
Optional APIs are needed for Schema Mount Aware instrumentation:
A new file "ncx/sm.h" is now available (starting in the 22.10T train).
- The APIs can often be used even if WITH_SCHEMA_MOUNT is not set.
In that case, all functions return NULL or an error.
Top-level SIL and SIL-SA code is not required to know anything about the specific mounted modules under a mount point. In fact stub code SIL code can be used in most cases for a module containing the mount point. SIL code must be installed for the mounted module SIL code to be invoked during edit operations.
The mounted modules usually need to know about the top-level module containing the mount point.
SIL and SIL-SA API calls that use path strings will have different forms:
The SIL-SA sub-system does not store modules in a hierarchy like the main server. Instead all API calls will be relative to the mount point root.
Extra path information has to be derived from the mount point instance information available to the callback function.
SIL and SIL-SA implementation restrictions apply:
A YANG module can appear once in each location. The revision, features, and deviations. can be different in each location.
Each subsystem can only implement a YANG module once.
A module can be implemented in multiple subsystems.
SIL and SIL-SA callbacks can both be loaded for the same objects, RPC operations and actions:
When a SIL library is loaded, the server will check the YANG library configuration for each location to determine if that module/revision is needed.
If a module is needed in a location, then the server will invoke the SIL "init1" callbacks, which will register the needed callbacks.
If multiple SIL libraries register for the same object, then the last one wins.
Do not use any modules a bundle or stand-alone SIL or SIL-SA which register for any object implemented by the server.
During subsystem registration, the "sil-sa-app" will determine if a module is needed in the subsystem. If so, then the server will invoke the SIL-SA "init1" callbacks, which will register the needed callbacks with the server.
Each SIL-SA subsystem should only register callbacks for one location.
It is possible for a SIL-SA subsystem to register callbacks for multiple locations, as long as no module is present in more than one location.
SIL and SIL-SA Code Generation
There are currently no additional APIs generated by yangdump-pro for Schema Mount. They must be added by existing SIL or SIL-SA hand at this time.
All of the existing auto-generated code is usable within a mount-point location, and none of it has to be changed.
The auto-generated SIL codestubs can be used directly with schema mount.
The SIL and SIL-SA callbacks for mounted modules are essentially the same as if the module is used in the top-level.
The path strings in the auto-generated SIL or SIL-SA code is always relative to a "location".
The path strings are the same whether the code is used at the top level or within a mount point.
There are some additional APIs added that may assist the SIL callback functions.
A "schema-mount aware" SIL callback function can access the "mount point instance" information.
For example, a module mounted under the ietf-interfaces "interface" list entry might need to know the interface name for the mount point instance.
A YANG module can be loaded in more than one location.
The module or bundle will be loaded into each location that is requested (i.e., top-level and/or 1 or more mount point identifiers).
The following SIL functionality is currently provided:
RPC callbacks
YANG Action callbacks
EDIT1, EDIT2 and EDIT3 callbacks
GET2 callbacks
Send notification APIs
Modules and Bundles can be used
The following SIL-SA functionality is currently provided:
RPC callbacks
YANG Action callbacks
EDIT1, EDIT2 and EDIT3 callbacks
GET2 callbacks
Modules and bundles can be used
Using a YANG Module in Multiple Locations
Each location is independent, but within the server deployment there are implementation restrictions and considerations for using the same YANG module.
Note
A library for a top-level module or bundle MUST NOT include any SIL callbacks for any modules that are already built into the netconfd-pro server.
It is not possible to provide your own top-level instrumentation to replace built-in server modules. Any such modification is not supported.
It is possible to provide your own SIL or SIL-SA libraries for mounted modules that already exist in the server, except ietf-yang-library.yang.
SIL Library Static Data is Shared
When a SIL library is loaded into the server, the 'static' data declared in any C modules is allocated only once. The same static data is accessed if the library is used in multiple locations.
It may not be safe to invoke SIL "init", "init2" and "cleanup" callbacks if they are invoked more than once.
Using Separate Libraries in Each Location
The safest approach is to create a separate bundle for each location. This creates a separate library with its own static data. Each bundle has a different name, but the modules can overlap or even be the same as another bundle in another location. The APIs are safe in this case because they are only invoked once.
Using the Same Library in Each Location
It is possible for a SIL library to be used in multiple locations, but coding restrictions apply.
Warning
If a SIL library is used in multiple locations, the static data is shared by all locations. Any static data added to the SIL code must account for this restriction.
For example, the standard init_static_vars
functions in the SIL code
with overwrite the previous values each time it is invoked.
This may causes memory leaks or server crashes.
The callback registration code must be invoked in each location. The schema tree for each location must be setup with the required SIL callbacks.
The server will assign SIL or SIL-SA callbacks to all locations where a given module is registered. The SIL code must use MPID information to determine the location for any protocol operation using the SIL callbacks.
The same SIL or SIL-SA library code will be invoked multiple times if multiple locations are found. There are important changes to the SIL or SIL-SA code that must be made in order for this to work.
Note
The SIL or SIL-SA entry point code must be updated to support usage in multiple locations:
All SIL External Interface functions are affected
The 'init_static_vars' and other code must be changed to insure it only runs once, even if invoked multiple times
All callback registration and unregistration must be done each time the callback is invoked.
Enabling Schema Mount
The schema mount feature has to be enabled in the system to be used. The --with-schema-mount and --sm-config CLI parameters control this behavior in the server.
In all other tools schema mount is enabled if WITH_SCHEMA_MOUNT=1 is used in the build.
The following API can be used to determine if schema mount is enabled:
-
boolean ncx_get_sm_enabled(void)
Get schema mount enabled flag.
On the server this flag will be set to false if there is no sm-config provided. This will save time and memory by ignoring mount-points that have no chance of being used.
- Returns:
TRUE ifr schema mount is enabled
Example YANG Module With a Mount Point
The following example module defines a simple mount point:
Module:
vtoaster
Label:
toaster
Path:
/vtoasters/vtoaster
Ancestor Keys:
id
module vtoaster {
yang-version 1.1;
namespace "urn:yumaworks:params:xml:ns:vtoaster";
prefix vtoast;
import ietf-yang-schema-mount {
prefix yangmnt;
}
revision 2023-02-20 {
description "Initial version.";
}
container vtoasters {
list vtoaster {
yangmnt:mount-point "toaster";
key id;
leaf id {
type uint8 {
range "1 .. max";
}
description
"Identifier for each virtual toaster.";
}
}
}
}
Schema Mount Root Control Block
A root control block (rootcb) is used to maintain the configuration and state data associated with a specific mount point identifier (module name and mount point label).
The 'ncx_sm_rootcb_t' struct is used to manage a root control block. It can be found in ncx/ncxtypes.h.
This structure should not be accessed directly. Use the API macros and functions described below.
There are no vendor-facing APIs for this data structure other than to use them to access the mount point instances (MPIDs) within the MP.
ncx_sm_rootcb_t Access
The following macros are defined in ncx/ncx.h The parameter R is a pointer to a ncx_sm_rootcb_t struct.
/* access label in rootcb */
#define ROOTCB_LABEL(R) (R)->label
/* access modname in rootcb */
#define ROOTCB_MODNAME(R) (R)->modname
/* access objpath field */
#define ROOTCB_OBJPATH(R) (R)->objpath
/* access rootobj field */
#define ROOTCB_ROOTOBJ(R) (R)->rootobj
The ncx/ncx.c and ncx/sm.c modules maintain the main schema mount data structures. The following functions can be used to access this list of ncx_sm_rootcb_t structs.
-
dlq_hdr_t *ncx_get_rootQ(void)
Get the Root control block queue.
- Returns:
Queue of root control blocks or NULL
-
ncx_sm_rootcb_t *ncx_first_rootcb(void)
Get the first Mount Point Control Block Entry in the rootcbQ.
- Returns:
Mount Point Entry if found or NULL if not
-
ncx_sm_rootcb_t *ncx_next_rootcb(ncx_sm_rootcb_t *rootcb)
Get the next Mount Point Entry in the rootcbQ.
- Parameters:
rootcb -- current Mount Point Entry
- Returns:
Mount Point Control Block Entry if found or NULL if not
-
void sm_dump_rootcb(ncx_sm_rootcb_t *rootcb)
Print the interesting fields in Root Control block Must be debug for some info, debug4 for full info.
—log-level=debug or higher
- Parameters:
rootcb -- Root Control Block to output
The following example shows possible output for sm_dump_rootcb:
Mount Point #1
Mount Point Parameters:
modname: vtoaster
label: toaster
root: /vtoast:vtoasters/vtoast:vtoaster
config: true
active: true
module: 'ietf-inet-types'
module: 'ietf-yang-library'
module: 'ietf-yang-types'
module: 'toaster'
-
void sm_dump_all_rootcb(void)
Print the interesting fields in all rootcbs.
In order to support yangcli-pro and netconfd-pro with the same code base, there is a "current rootcb" maintained in ncx/ncx.c.
-
ncx_sm_rootcb_t *ncx_get_cur_rootcb(void)
Gte the current root control block value.
Get the current root control block value
- Returns:
rootcb if any
-
void ncx_set_cur_rootcb(ncx_sm_rootcb_t *rootcb)
Set the current root control block.
Set the current root control block value
- Parameters:
rootcb -- Root control block to set as current
There is a rootcb created for each mount-point that matches a mount point identifier, found in the config file specified in the --sm-config CLI parameter.
The rootcb is stored in the obj_template_t for the container or list of the mount point object. The following functions can be used with the obj_template_t struct:
-
boolean obj_is_mp_with_rootcb(const obj_template_t *obj)
Check if current object is Mount Point object.
Check if current object is Mount Point object and its rootcb if setup correctly
- Parameters:
obj -- object to check
- Returns:
TRUE if this object is a Mount Point object with rootcb; FALSE otherwise
-
ncx_sm_rootcb_t *obj_find_ancestor_rootcb(obj_template_t *obj)
Find an ancestor node or self that has a rootcb.
- Parameters:
obj -- object to check
- Returns:
pointer to rootcb if found, NULL otherwise
-
boolean obj_is_mounted(obj_template_t *child)
Check if mounted object.
Check if this object is a mounted child for particular Mount Point
- Parameters:
child -- object to check
- Returns:
TRUE if this object is mounted object; FALSE otherwise
ncx_sm_mpid_t Access
An MPID contains the internal data structures to manage the instance of the mount point root. It also contains the fields such as the list of ancestor keys.
The MPID is stored in the 'val_extra' struct within the val_value_t struct for the container or list instance for the mount point root.
The following API in ncx/val.h can be used to access the MPID in a value node:
-
ncx_sm_mpid_t *val_get_mpid(const val_value_t *val)
Get the MPI.
Get the back-ptr to the Moint Point ID struct
- Parameters:
val -- value to check
- Returns:
back-ptr to the Moint Point ID struct
Note
SIL and SIL-SA code is always written as if it is mounted at the top-level.
The MPID for a mounted data node must be accessed to determine instance information for nodes above the mount point.
A schema mount aware callback function can access the mount point object and any ancestor key leafs needed to identify one data node instance of the mount point root.
-
struct ncx_sm_mpid_t
Moint Point Instance This struct lives in a val_value_t.val_extra struct.
It is created when the val_value_t is parsed and created This is used by internal code and SIL code to identify the ancestor keys above the mount point.
Public Members
-
dlq_hdr_t qhdr
queue header
-
dlq_hdr_t anckeyQ
Q of malloced val_value_t; 1 entry for each key leaf includes the ancestor keys.
-
struct val_value_t_ *valroot
Backptr to the value node that contains this MPID This is a SIL only field.
The SIL-SA version will be NULL
-
ncx_backptr_t backptr
inline backptr used to store this MPID in the rootcb->mpidQ.
The rootcb is stored in the obj_template_t for the SM root object. This is not used in SIL-SA.
-
boolean backptr_queued
flag to indicate the backptr is actually in a rootcb->mpidQ.
This is not used in SIL-SA.
-
xmlChar *module
Set for SIL-SA ONLY: module name for the mount point This is a malloced string.
-
xmlChar *label
Set for SIL-SA ONLY: label for the mount point This is a malloced string.
-
xmlChar *objpath
Set for SIL-SA ONLY: path string for the object template This is a malloced string.
-
dlq_hdr_t qhdr
The Mount Point root control block can be accessed with the following API:
-
ncx_sm_rootcb_t *sm_get_rootcb_for_mpid(ncx_sm_mpid_t *mpid)
Get the rootcb for an MPID.
Should work on SIL. May not work on SIL-SA
- Parameters:
mpid -- MPID to check
- Returns:
pointer to the rootcb or NULL if cannot be accessed
The following API can be used in "debug" mode to output MPID information to the server log:
-
void sm_dump_mpid(const ncx_sm_mpid_t *mpid)
Dump the contents of a MPID struct if DEBUG.
This is an external API that can be used in SIL code
Non-const needed for val_dump_value; not modified
- Parameters:
mpid -- Mount Point ID control block to dump
The following example shows possible output for sm_dump_mpid if "debug4" log-level is used:
sm: MPID info
MP valroot: toaster
MP module: vtoaster
MP label: toaster
MP objpath: /vtoast:vtoasters/vtoast:vtoaster
anckeyQ (1):
id 2
The following APIs can be used in retrieve the MPIDs for a given rootcb:
-
ncx_sm_mpid_t *sm_get_first_mpid(ncx_sm_rootcb_t *rootcb)
Get the first MPID struct in the rootcb.
- Parameters:
rootcb -- root control block to check
- Returns:
pointer to first MPID or NULL if none
-
ncx_sm_mpid_t *sm_get_next_mpid(ncx_sm_mpid_t *curmpid)
Get the next MPID struct in the rootcb.
- Parameters:
curmpid -- current MPID to get next for
- Returns:
pointer to next MPID or NULL if none
The following APIs can be used in retrieve the ancestor keys from an MPID:
-
val_value_t *sm_get_first_anckey(const ncx_sm_mpid_t *mpid)
Get first ancestor key from the MPID.
- Parameters:
mpid -- Mount Point Instance Descriptor to use
- Returns:
pointer to the first entry or NULL if empty Q
-
val_value_t *sm_get_next_anckey(const val_value_t *curkey)
Get next ancestor key from the MPID.
- Parameters:
curkey -- current kley entry
- Returns:
pointer to the next entry or NULL if none
The following code snippet shows how MPID key leafs can be accessed:
/**
* @brief Compare MPID ancestor keys
*
* @param mpid1 test MPID 1
* @param mpid2 test MPID 2
* @return compare result
*/
static int32
compare_mpid_keys (ncx_sm_mpid_t *mpid1,
ncx_sm_mpid_t *mpid2)
{
val_value_t *key1 = sm_get_first_anckey(mpid1);
val_value_t *key2 = sm_get_first_anckey(mpid2);
while ((key1 != NULL) && (key2 != NULL)) {
int32 ret = val_compare(key1, key2);
if (ret != 0) {
return ret;
}
key1 = sm_get_next_anckey(key1);
key2 = sm_get_next_anckey(key2);
}
if ((key1 == NULL) && (key2 == NULL)) {
return 0;
}
return (key1 == NULL) ? -1 : 1;
} /* compare_mpid_keys */
Schema Mount APIs for EDIT Operations
All EDIT2 Callback and EDIT3 Callback transactions are conducted the same for a schema-mounted module.
The server handles fully integrated schema tree access from the top data node to the leafs in a mounted module. The top-level objects in the mounted modules are just child nodes of the mount point object.
Note
The registration and unregistration of EDIT callbacks is done exactly as for the top-level. The path statements are relative to the mount point root, not the top-level root.
All YANG validation and XPath evaluation from a mounted data node are done relative to the mount point. The MPI data node is used as the document root, not the real root.
Note
All edits for mounted configuration data are handled with top-level operations (e.g., <edit-config>).
Mounted data can be included in an edit <config> element just like any other data nodes.
Edit operations to create the MPI node and/or any mounted data nodes can be done separately or all at once.
The SIL or SIL-SA callback code only needs to access the MPID for a specific edit operation if it is schema mount aware. The following API can be used for this purpose, e.g., for the 'newval' or 'curval' in an edit transaction "undo" record.
-
ncx_sm_mpid_t *sm_get_ancestor_mpid(val_value_t *val)
Get the MPID for the specified mounted node.
This is an API for SIL or SIL-SA callback code
- Parameters:
val -- value to check
- Returns:
back-ptr to the MPID fot the MP ancestor or NULL if none or some error
The following example shows how some SIL callback code might use these functions:
/* get the ancestor MPID from a mounted configuration data node */
ncx_sm_mpid_t *mpid = sm_get_ancestor_mpid(newval);
if (mpid != NULL) {
/* get the mount point rootcb */
ncx_sm_rootcb_t *rootcb = sm_get_rootcb_for_mpid(mpid);
}
Schema Mount APIs for GET2 Operations
The GET2 Callback function works the same for mounted modules as it does for top-level modules. Only the ancestor nodes (and list key leafs) for the data nodes within the mount point are accessible through the normal GET2 API functions.
Note
The registration and unregistration of GET2 callbacks is done exactly as for the top-level. The path statements are relative to the mount point root, not the top-level root.
The MPID in use is available to a GET2 callback using the following macro:
/* Schema mount MPID field
* The parameter G is a getcb_get2_t *
*/
#define GETCB_GET2_SM_MPID(G) (G)->sm_mpid
The 'sm_mpid' fields will be set to a pointer to the ncx_sm_mpid_t struct for the ancestor mount point root for the "obj" in the GET2 callback. It will be set to 'NULL' if there is no ancestor MPID. The new toaster SIL module retrieves the toaster 'id' from the MPID and maintains an array of toasters instead of 1 toaster.
status_t u_toast_toasterStatus_get (
getcb_get2_t *get2cb)
{
// ....
/* get the ancestor MPID node (if any) for this object */
status_t res = NO_ERR;
uint8 id = 0;
ncx_sm_mpid_t *mpid = GETCB_GET2_SM_MPID(get2cb);
if (mpid != NULL) {
res = get_id_from_mpid(mpid, &id);
if (res != NO_ERR) {
return res;
}
}
// ...
}
Schema Mount APIs for RPC Operations and Actions
An RPC operation in a mounted module is available as an "action" in the top-level server. Within the server the RPC and action callback APIs are different, so a mounted RPC still looks like an RPC operation to the SIL or SIL-SA code.
The only difference is that new optional APIs are available to identify the mount point instance. To retrieve the MPID information provided in the message input from the client:
Note
The registration and unregistration of RPC or action callbacks is done exactly as for the top-level. The path statements for actions are relative to the mount point root, not the top-level.
-
ncx_sm_mpid_t *sm_get_mpid_from_msg(rpc_msg_t *msg)
Get the MPID struct from the message header if set.
This field is set by the server during RPC or action processing so SIL code in schema mounted modules can identify which mount point instance is being used by the client
- Parameters:
msg -- RPC msg message header to check
- Returns:
pointer to the MPID struct if any set
This function provides a parsed MPID, if any. It is not verified to be correct or match an existing MPID in the server.
RPC Example
The following "invoke callback" shows how the new APIs can be used.
First, add an new include statement to the SIL module:
#include "sm.h"
Next, add code to get and display the mount point instance information.
status_t u_t60_rpc1_invoke (
ses_cb_t *scb,
rpc_msg_t *msg,
xml_node_t *methnode)
{
status_t res = NO_ERR;
if (LOGDEBUG) {
log_debug("\nStart SIL invoke rpc <rpc1> from module t60");
}
val_value_t *inputval = agt_get_rpc_input(msg);
if (inputval == NULL) return ERR_NCX_OPERATION_FAILED;
ncx_sm_mpid_t *mpid = sm_get_mpid_from_msg(msg);
if (mpid && LOGDEBUG) {
sm_dump_mpid(mpid);
}
val_value_t *v_src_addr_val = NULL;
// ....
}
Schema Mount APIs for Notifications
NETCONF notification delivery can be used from a mounted module. This applies to top-level notifications and nested notifications.
A notification "send" function SHOULD pick a mount point instance to be the ancestor nodes used in the notification. Use APIs described above to access the MPIDs in use for a given mount point root.
If no MPID is selected then the first MPID found will be used.
Note
If no Mount Point Instances exist for a mounted module, but the SIL or SIL-SA code queues a notification anyway, then the notification will be dropped and not sent to any subscriptions.
Selecting an MPID to Use
The "send" function is called from system code. It is never called from the server since it is not a callback. Therefore there is no MPID for the server to provide to a notification send function. It must know or find the correct MPID to use in the notification.
The MPID is used by the server to generate the ancestor key values for the notification message.
If there are no ancestor keys because the mount point has no ancestor lists (only one or more containers), then no MPID needs to be set.
If only only instance of the mount point exists then the default (first entry) will be the only entry so no MPID needs to be set.
In most cases the "send" function will need internal state information to know the correct instance associated with the notification event.
Getting the RootCB for the Notification
If the notification is used in multiple locations with the same library, the the correct mount point root must first be determined. The :ref"ncx_sm_rootcb_t Access APIs can be used to find the correct root control block to use.
The notification object is saved in static data by the initialization function. If not then the "send" function must find it somehow to determine the correct mount point for the notification.
If the notification is used in only one location then there will be only one mount point root for it. In this case the "obj_find_ancestor_rootcb" API can be used to find the correct rootcb.
The following API can be used to set the MPID value in the notification before it is queued for delivery.
-
status_t agt_not_set_notif_mpid(agt_not_msg_t *msg, const ncx_sm_mpid_t *mpid)
Set the MPID to use for sending the schema-mounted notification.
The MPID is copied. It could be a real MPID from a valroot or a fake MPID constructed for this API
Not needed if there is only one instance of the mount point. Needed to provide the ancestor keys to use in the notification
- Parameters:
msg -- message to set the MPID for
mpid -- MPID structure to clone and save. A copy will be made so this mpid can be deleted after this call
- Returns:
status
Example: Searching the Ancestor MPIDs
In this example one mount point is assumed and the MPIDs for the rootcb are searched. An MPID could also be constructed. This is not supported with direct APIs at this time.
agt_not_msg_t *notif = agt_not_new_notification(not2_obj);
if (notif == NULL) {
log_error("\nError: malloc failed, cannot send "
"<not2> notification");
return;
}
#ifdef WITH_SCHEMA_MOUNT
ncx_sm_rootcb_t *rootcb = obj_find_ancestor_rootcb(not2_obj);
if (rootcb != NULL) {
boolean found = false;
ncx_sm_mpid_t *mpid = sm_get_first_mpid(rootcb);
for (; mpid; mpid = sm_get_next_mpid(mpid)) {
/* examine the ancestor keys
* The SIL code must know what to expect here
*/
val_value_t *keyval = sm_get_first_anckey(mpid);
for (; keyval; keyval = sm_get_next_anckey(keyval)) {
/* Use val_compare to check value not shown here
* set done=true if MPID is the one to use
*/
}
/* if found then set the mpid and break */
if (found) {
/* this function can fail; should check status */
(void)agt_not_set_notif_mpid(notif, mpid);
break;
}
}
}
#endif // WITH_SCHEMA_MOUNT
Toaster SIL Code Modifications
The toaster module shows a special case example where the MPID used in an RPC operation needs to be remembered so a notification can be sent later.
The toaster.c example code has been updated to be Schema Mount aware. Note that this SIL code has been updated to the current API set. The contents of "toaster.c" have been migrated to "u_toaster.c".
The following changes are made to support schema mount:
Include "sm.h" in the C file
Make sure make command and Makefile properly sets WITH_SCHEMA_MOUNT
The toaster state has been moved from one instance to an array of structures.
A variable called
toaster_mpid
is added to the state of each toasterA clone of the MPID used in the "make-toast" operation is saved for the "toastDone" notification. The real MPID cannot be saved. It can only be used within the RPC message.
The sm_clone_mpid API must be used so the MPID can be saved**
The MPID is added to the "toastDone" notification when it is generated
The saved MPID is freed when the notification is sent or when the module cleanup API is invoked
If a client accesses any object or RPC operation then it checks for an MPID.
If none, then the top-level toaster (ID=0) is used.
If a valid ID (1 .. 8) is found, then the proper toaster state is referenced.
If an invalid ID is found, then a ERR_NCX_NO_INSTANCE error is returned to the client.
Get the toaster ID from the MPID:
The 'vtoaster' list is indexed by a 'uint8' key leaf, called 'id'. The 'get_id_from_mpid' function shows how the 1 expected key can be accessed.
static status_t
get_id_from_mpid (ncx_sm_mpid_t *mpid,
uint8 *retnum)
{
if ((mpid == NULL) || (retnum == NULL)) {
return ERR_NCX_OPERATION_FAILED;
}
val_value_t *keyval = sm_get_first_anckey(mpid);
if (keyval == NULL) {
return ERR_NCX_MISSING_PARM;
}
if (VAL_TYPE(keyval) != NCX_BT_UINT8) {
return ERR_NCX_INVALID_VALUE;
}
uint8 num = VAL_UINT8(keyval);
if ((num == 0) || (num > TOASTER_CNT)) {
return ERR_NCX_NO_INSTANCE;
}
*retnum = num;
return NO_ERR;
} /* get_id_from_mpid */
Save the MPID:
The "make-toast" operation callback function must save the MPID used by the client, so it can be used for the "toastDone" notification sent after the toast is done or cancelled.
Warning
Any MPID that is accessed from a message or val_value_t must be cloned to be used after the callback function is finished.
-
ncx_sm_mpid_t *sm_clone_mpid(const ncx_sm_mpid_t *mpid, val_value_t *valroot)
Clone an existing MPID.
- Parameters:
mpid -- Mount Point ID control block to clone
valroot -- value node getting this MPID
may be NULL
back pointer to the valroot not malloced pointer
- Returns:
clone of mpid must be freed with sm_free_mpid
An example can be found in the u_toaster.c file. The first code snippet shows how the RPC operation retrieves the MPID used by the client.
uint8 id = 0;
ncx_sm_mpid_t *mpid = sm_get_mpid_from_msg(msg);
if (mpid != NULL) {
res = get_id_from_mpid(mpid, &id);
if (res != NO_ERR) {
return res;
}
}
toaster_state_t *state = &toaster_state[id];
The following code snippet shows how the old MPID is deleted in case it is set and the new MPID is cloned and saved if schema mount is not enabled then the MPID will be NULL and the toaster_state[0] state record will be used.
if (res == NO_ERR) {
state->toaster_toasting = TRUE;
sm_free_mpid(state->toaster_mpid);
state->toaster_mpid = NULL;
if (mpid) {
state->toaster_mpid = sm_clone_mpid(mpid, NULL);
// not checking for NULL here to avoid ifdef code
}
} else {
agt_record_error(
scb,
&msg->mhdr,
NCX_LAYER_OPERATION,
res,
methnode,
NCX_NT_NONE,
NULL,
NCX_NT_NONE,
NULL);
}
Tutorial: Schema Mounted Virtual Toaster
The YumaPro Quickstart Guide has a tutorial to introduce the toaster.yang module. This tutorial assumes some familiarity with that introduction.
Summary:
Build and install the SIL for the Schema Mount root module
Configure and start the server for this example
Connect to the server with yangcli-pro: - Enable the Toaster for the Mounted Toaster - Start Making Toast for the Mounted Toaster - Receive the 'toastDone' Notification
Build the libvtoaster.so SIL Library
Note
There must be SIL code installed for the YANG module(s) containing the mount point.
All configuration ancestor nodes and the mount point itself must have EDIT callbacks installed for the mounted SIL callbacks to be invoked,
The make_sil_dir_pro script can be used to generate dummy SIL callbacks
for the 'vtoaster.yang' module. This module is usually installed in
the /usr/share/yumapro/modules/test/pass
directory.
Generate the SIL Code Stubs
lab1$ make_sil_dir_pro vtoaster
modparms =
cmnparms = --indent=4 --module=vtoaster --unified=true
*** /usr/share/yumapro/modules/test/pass/vtoaster.yang
*** 0 Errors, 0 Warnings
*** /usr/share/yumapro/modules/test/pass/vtoaster.yang
*** 0 Errors, 0 Warnings
*** /usr/share/yumapro/modules/test/pass/vtoaster.yang
*** 0 Errors, 0 Warnings
*** /usr/share/yumapro/modules/test/pass/vtoaster.yang
*** 0 Errors, 0 Warnings
Run the following commands to get started:
cd vtoaster
make doc
make opendoc
lab1$
Make and Install the SIL Code
Note that any warnings for unused parameters can be ignored. The empty code stubs should be implemented but that is not required to enable the Schema Mount SIL (or SIL-SA) code in the toaster module.
lab1$ cd vtoaster
lab1$ make DEBUG=1 WITH_SCHEMA_MOUNT=1
...
lab1$ sudo make DEBUG=1 WITH_SCHEMA_MOUNT=1 install
If the 'pthreads' library is used then add PTHREADS=1
to
each command. Do not set PTHREADS unless the server
is also build with this flag.
lab1$ cd vtoaster
lab1$ make DEBUG=1 WITH_SCHEMA_MOUNT=1 PTHREADS=1
...
lab1$ sudo make DEBUG=1 WITH_SCHEMA_MOUNT=1 PTHREADS=1 install
Configure and Run the Server
Create a Server Config File
The following netconfd-pro configuration
file called toast.conf
is assumed to be installed in the /home/lab1
directory.
netconfd-pro {
log-level debug4
access-control off
no-watcher
sm-config $HOME/toast.json
module vtoaster
}
Create a Schema Mount Config File
The following netconfd-pro sm-config JSON
file called toast.json
is assumed to be installed in the HOME
directory for the user.
This configuration specifies that the 'toaster' module will
be mounted under the mount point named 'toaster' in top-level
module 'vtoaster'.
{
"yumaworks-schema-mount:schema-mount" : {
"sm-config" : [
{
"mp-module" : "vtoaster",
"mp-label" : "toaster",
"mp-config" : true,
"mp-cli" : {
"module" : [ "toaster" ]
}
}
]
}
}
Start the Server
The server can be started as follows for this example:
> netconfd-pro --config=/home/lab1/toast.conf
Connect to the Server with yangcli-pro
The yangcli-pro tool is used for this example.
> yangcli-pro --message-indent=1
Use the connect command to start a NETCONF session with the server.
If the schema-mount aware session is started successfully, there will be some mount point information printed when the session starts.
Example Schema Mount Session Report:
Schema Mount Point:
modname: vtoaster
label: toaster
root: /vtoast:vtoasters/vtoast:vtoaster
config: true
active: true
YANG Library:
ietf-inet-types@2013-07-15
ietf-yang-library@2016-06-21
ietf-yang-types@2013-07-15
toaster@2009-11-20
Create the Top Level and Mounted Config Data All at Once
yangcli-pro and netconfd-pro can create and modify top-level and mounted configuration data nodes in the same edit. The YANG data nodes created for mounted modules are no different than other child nodes within a container or list.
Example Using merge command
In this example the merge command is used to interactively fill in some
configuration data. The /vtoasters/vtoaster
mount point is created and
the /toaster
presence container node is created to enable the toaster.
Virtual toasters '1' and '2' are created and enabled in the same edits.
lab1> merge /vtoasters -opt
Filling container /vtoasters:
Add optional list 'vtoaster'?
Enter Y for yes, N for no, or C to cancel: [default: Y]
lab1:merge>
Filling list /vtoasters/vtoaster:
Filling key leaf /vtoasters/vtoaster/id:
Enter uint8 value for leaf <id>
lab1:merge> 1
Add optional container 'toaster'?
Enter Y for yes, N for no, or C to cancel: [default: Y]
lab1:merge>
Filling container /vtoasters/vtoaster/toaster:
Add another entry for list 'vtoaster'?
Enter Y for yes, N for no, or C to cancel: [default: N]
lab1> y
Filling list /vtoasters/vtoaster:
Filling key leaf /vtoasters/vtoaster/id:
Enter uint8 value for leaf <id>
lab1> 2
Add optional container 'toaster'?
Enter Y for yes, N for no, or C to cancel: [default: Y]
lab1>
Filling container /vtoasters/vtoaster/toaster:
Add another entry for list 'vtoaster'?
Enter Y for yes, N for no, or C to cancel: [default: N]
lab1>
RPC OK Reply 4 for session 3 [default]:
lab1> commit
RPC OK Reply 5 for session 3 [default]:
lab1>
The XML message for the edit is shown below:
rpc message-id="4"
xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<edit-config>
<target>
<candidate/>
</target>
<default-operation>merge</default-operation>
<test-option>set</test-option>
<config>
<vtoasters
xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"
nc:operation="merge"
xmlns="urn:yumaworks:params:xml:ns:vtoaster">
<vtoaster>
<id>1</id>
<toaster xmlns="http://netconfcentral.org/ns/toaster"/>
</vtoaster>
<vtoaster>
<id>2</id>
<toaster xmlns="http://netconfcentral.org/ns/toaster"/>
</vtoaster>
</vtoasters>
</config>
</edit-config>
</rpc>
Example Using Configuration Mode Editing
The "config mode" editing can also create top-level and mounted data all at once.
Virtual toasters '3' and '4' are created and enabled in this example.
lab1> config term
lab1# vtoasters
lab1(vtoasters)# vtoaster 3 toaster
lab1(toaster)# exit
Applying 1 edit
lab1(vtoaster)# exit
lab1(vtoasters)# vtoaster 4 toaster
lab1(toaster)# apply
Applying 1 edit
lab1(toaster)# return
lab1>
Example <rpc> Request
The XML message for vtoaster '3' is shown below:
<?xml version="1.0" encoding="UTF-8"?>
<rpc message-id="11"
xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<edit-config>
<target>
<candidate/>
</target>
<default-operation>merge</default-operation>
<test-option>set</test-option>
<config>
<vtoasters xmlns="urn:yumaworks:params:xml:ns:vtoaster">
<vtoaster>
<id>4</id>
<toaster xmlns="http://netconfcentral.org/ns/toaster"/>
</vtoaster>
</vtoasters>
</config>
</edit-config>
</rpc>
Example <get-config>
The <get-config> RPC operation can be used to retrieve the running configuration. It will include the mount point configuration data as well.
> get-config source=running
RPC Data Reply 6 for session 3 [default]:
rpc-reply {
data {
vtoasters {
vtoaster 1 {
id 1
toaster {
}
}
vtoaster 2 {
id 2
toaster {
}
}
vtoaster 3 {
id 3
toaster {
}
}
vtoaster 4 {
id 4
toaster {
}
}
}
}
}
The XML response from the server is show below:
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply message-id="14"
xmlns:ncx="http://netconfcentral.org/ns/yuma-ncx"
ncx:last-modified="2023-02-25T01:09:49Z" ncx:etag="79065"
xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<data>
<vtoasters xmlns="urn:yumaworks:params:xml:ns:vtoaster">
<vtoaster>
<id>1</id>
<toaster xmlns="http://netconfcentral.org/ns/toaster"></toaster>
</vtoaster>
<vtoaster>
<id>2</id>
<toaster xmlns="http://netconfcentral.org/ns/toaster"></toaster>
</vtoaster>
<vtoaster>
<id>3</id>
<toaster xmlns="http://netconfcentral.org/ns/toaster"></toaster>
</vtoaster>
<vtoaster>
<id>4</id>
<toaster xmlns="http://netconfcentral.org/ns/toaster"></toaster>
</vtoaster>
</vtoasters>
</data>
</rpc-reply>
Make Toast in the Mounted Toaster
After the /top/list1/mp1/toaster
container is created,
the 'make-toast' RPC operation can be used.
Note
RPC operations in mounted modules are invoked with the NETCONF <action> operation.
The action command in yangcli-pro can be used to invoke RPC operations in mounted modules.
In order to fill the optional input parameters, the --opt
parameter is used
Create vtoaster 1:
lab1> action /vtoasters/vtoaster[id=1]/make-toast -opt
Filling optional mounted RPC /vtoasters/vtoaster/make-toast:
Enter uint32 value for leaf <toasterDoneness> [5]
lab1:action> 3
Enter identityref value for leaf <toasterToastType> [toast:wheat-bread]
lab1:action> frozen-waffle
RPC OK Reply 16 for session 3 [default]:
The XML message for this request:
<?xml version="1.0" encoding="UTF-8"?>
<rpc message-id="17"
xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<action xmlns="urn:ietf:params:xml:ns:yang:1">
<vtoasters xmlns="urn:yumaworks:params:xml:ns:vtoaster">
<vtoaster>
<id>3</id>
<make-toast xmlns="http://netconfcentral.org/ns/toaster">
<toasterDoneness>5</toasterDoneness>
<toasterToastType xmlns:toast="http://netconfcentral.org/ns/toaster">toast:wheat-bread</toasterToastType>
</make-toast>
</vtoaster>
</vtoasters>
</action>
</rpc>
Create vtoaster 3:
lab1> action /vtoasters/vtoaster[id=3]/make-toast -opt
Filling optional mounted RPC /vtoasters/vtoaster/make-toast:
Enter uint32 value for leaf <toasterDoneness> [5]
lab1:action>
Enter identityref value for leaf <toasterToastType> [toast:wheat-bread]
lab1:action>
RPC OK Reply 17 for session 3 [default]:
lab1>
The XML message for this request:
<?xml version="1.0" encoding="UTF-8"?>
<rpc message-id="17"
xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<action xmlns="urn:ietf:params:xml:ns:yang:1">
<vtoasters xmlns="urn:yumaworks:params:xml:ns:vtoaster">
<vtoaster>
<id>3</id>
<make-toast xmlns="http://netconfcentral.org/ns/toaster">
<toasterDoneness>5</toasterDoneness>
<toasterToastType
xmlns:toast="http://netconfcentral.org/ns/toaster">toast:wheat-bread</toasterToastType>
</make-toast>
</vtoaster>
</vtoasters>
</action>
</rpc>
Receive the ToastDone Notifications
After about 36 seconds the 'toastDone' notification should be received from the first vtoaster server. The yangcli-pro console output will show the entire notification message.
Incoming notification for session 1 [default]:
notification {
eventTime 2023-02-25T01:27:56.852609Z
vtoasters {
vtoaster {
id 1
toastDone {
toastStatus done
}
}
}
}
lab1>
The XML message from the server is shown below:
<?xml version="1.0" encoding="UTF-8"?>
<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0">
<eventTime>2023-02-25T01:27:56.852609Z</eventTime>
<vtoasters xmlns="urn:yumaworks:params:xml:ns:vtoaster">
<vtoaster>
<id>1</id>
<toastDone xmlns="http://netconfcentral.org/ns/toaster">
<toastStatus>done</toastStatus>
</toastDone>
</vtoaster>
</vtoasters>
</notification>
After another 20 seconds the second notification for vtoaster '3' should arrive:
Incoming notification for session 1 [default]:
notification {
eventTime 2023-02-25T01:28:36.012403Z
vtoasters {
vtoaster {
id 3
toastDone {
toastStatus done
}
}
}
}
The XML message from the server is shown below:
<?xml version="1.0" encoding="UTF-8"?>
<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0">
<eventTime>2023-02-25T01:28:36.012403Z</eventTime>
<vtoasters xmlns="urn:yumaworks:params:xml:ns:vtoaster">
<vtoaster>
<id>3</id>
<toastDone xmlns="http://netconfcentral.org/ns/toaster">
<toastStatus>done</toastStatus>
</toastDone>
</vtoaster>
</vtoasters>
</notification>
Long-Term Usage Limitations
In addition to the Unsupported Features there are some usage restrictions within the netconfd-pro server.
A mount point node must be a configuration node. It cannot be an operational node (e.g. provided by a GET2 callback).
Only GET2 callbacks are allowed for operational data. Static val_value_t nodes and GET1 callbacks are not supported
Only EDIT2, EDIT3 callbacks should be used since EDIT1 callback mode is deprecated.
Only real object templates can be used to generate val_value_t data nodes for use in the server. Generic objects MUST NOT be used.
Mounted modules cannot be loaded or unloaded at run-time. Only the --sm-config CLI parameter can be used to load modules into a mount point.
There must be SIL callbacks installed for the mount point root and all its ancestors. If not, the SIL and SIL-SA callbacks for mounted objects can get skipped.
The mount point root object cannot contain any extra child nodes:
For a container, there must not be any children defined.
For a list, there must not be any children defined except the list keys.
There must not be any modules augmenting the mount point object.
TBD: it is possible that not all implementation limitations are known yet
Temporary Usage Limitations
Server Issues
Only one data node can be a mount point for a given label. If the same mount-point extension appears in multiple nodes in the same module then only the first node encountered will be supported as a mount point. The rest of the nodes using the same label will be ignored. Use a deviation-stmt to set all these ignored nodes to 'not-supported'.
Since this module is the one defined the labels, just pick a different label name for each desired mount point.
DELETE on node above the MP root can skip SIL callbacks for mounted objects
Each SIL-SA subsystem can support a single location.
Current implementation does not identity which mount point should be used for each module registered by each subsystem.