Schema Mount Server APIs

Warning

  • This section is preliminary and under development.

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.

Note

The server maintains different sets of modules for the top-level and each mount point.

  • The same YANG library configuration and SIL or SIL-SA callbacks are applied to all data nodes containing the same mount point identifier.

  • The modules are not shared across mount point identifiers

  • The modules, revisions, features, deviations, and annotations are independent for each mount point identifier.

  • The SIL and SIL-SA callbacks for mounted modules are essentially the same as if the module is used in the top-level.

  • There are some additional APIs added that may assist the SIL callback functions.

  • A "schema-mount aware" SIL callback function can accesss 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 specified in more than 1 bundle and in more than 1 mount point.

  • 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).

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 change. The code is generated and written as if it is located at the top-level. Optional APIs can be used to read the instance information for a mount point.

Top-level SIL and SIL-SA code is not required to know anything about the specific mounted modules under a mount point. 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.

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 dummy example module defines a simple mount point:

  • Module: sm-test2

  • Label: mp1

  • Path: /top/list1/mp1

  • Ancestor Keys: k

module sm-test2 {
  yang-version 1.1;
  namespace "urn:yumaworks:params:xml:ns:sm-test2";
  prefix sm2;

  import ietf-yang-schema-mount {
    prefix yangmnt;
  }

  revision 2022-07-24 {
    description "Initial revision.";
  }

  container top {
    list list1 {
      key k;
      leaf k { type uint32; }
      leaf col1 { type string; }
      container mp1 {
        yangmnt:mount-point "mp1";
      }
    }
  }
}

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:

MP parameters:
 modname: sm-test2
 label: mp1
 rootobj name: mp1
 config: true
  module: 'ietf-inet-types'
  module: 'ietf-yang-library'
  module: 'ietf-yang-types'
  module: 'test3'
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

Check if current object is Mount Point object and its rootcb if setup correctly

Parameters
  • obj -- object to check

  • obj -- object to check

Returns

TRUE if this object is a Mount Point object with rootcb; FALSE otherwise

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

Check if this object is a mounted child for particular Mount Point

Parameters
  • child -- object to check

  • child -- object to check

Returns

TRUE if this object is mounted object; FALSE otherwise

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

Get the back-ptr to the Moint Point ID struct

Parameters
  • val -- value to check

  • val -- value to check

Returns

back-ptr to the Moint Point ID struct

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.

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(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: mp1
  MP module: sm-test2
  MP label: mp1
  MP objpath: /sm2:top/sm2:list1/sm2:mp1
  anckeyQ (1):
  k 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(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(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 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.

status_t u_t60_npcon2_get (
    getcb_get2_t *get2cb)
{

    /* get the ancestor MPID node (if any) for this object */
    ncx_sm_mpid_t *mpid = GETCB_GET2_SM_MPID(get2cb);

    // ...
}

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.

The following API can be used to set the MPID value in the notification before it is queued for delivery.

void agt_not_set_notif_mpid(agt_not_msg_t *msg, ncx_sm_mpid_t *mpid)

Set the MPID to use for sending the schema-mounted notification.

Ignored if any programming errors like NULL pointers

Parameters
  • msg -- message to set the MPID for

  • mpid -- MPID pointer to set

Using a Library in Multiple Locations

It is possible for a module to be supported in multiple locations, where a location is either the top-level, or a specific mount point identifier. Typically a module is only used in one location.

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.

Warning

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.

TBD:Example

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 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 (22.10T-2)

  • 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

    • RPC and action input XPath validation may not be correct for YANG 1.1 modules that reference data outside RPC or action node.

  • No code generation support for schema mount APIs yet

    • All new schema mount APIs are optional to use so nothing preventing the use of existing SIL code.

    • All existing SIL and SIL-SA code generation and APIs will continue to work.

    • SIL code generation for mounted modules requires the following manual edits at this time:

      • Add an include statement for "sm.h" to the C file (e.g., u_foo.c)

        #include "sm.h"
        
      • Set the SUBDIR_CPP variable in the src/Makefile

        SUBDIR_CPP=-DWITH_SCHEMA_MOUNT=1