../_images/logo.png

YumaPro Developer Manual

Developer Quickstart

YumaPro Tools

../_images/software_overview.png

Refer to the YumaPro User Manual for a complete introduction to YumaPro Tools.

This section focuses on the software development aspects of NETCONF, YANG, and the netconfd-pro server.

YANG provides a completely programmable management API.

  • A specific YANG module defines how to do something (e.g, ietf-yang-push.yang) and the server binds the instrumentation to the specified YANG objects.

  • Callback functions are used to hook device and data model specific behavior to database objects and RPC operations.

  • The yangdump-pro program is used to generate the glue code and instrumentation code stubs

  • The developer fills in the YANG instrumentation code.

  • The completed code is built and loaded into the server at compile-time, boot-time, or run-time.

What is a Developer Expected to Implement?

A server developer must provide the server instrumentation to perform the management changes to the system requested by a client. The most common YANG statements that need instrumentation:

Database Deployment Variants

There are many ways to configure and build the server for storing and retrieving datastore contents.

  • Local internal database: configuration in memory and NV-storage in netconfd-pro owned XML file

  • Local internal database + NV-store hook: configuration in memory and NV-storage via callback functions, and transferred to/from the server as an XML file

  • Local internal database + no-NV-store: configuration in memory and NV-storage is managed by the yp-system and/or SIL code. The server will not attempt to load or store the configuration to non-volatile storage

  • Local internal database + external edits: your external process communicates with the netconfd-pro process to initiate external edits (e.g., initiated from legacy CLI or internal database)

Local internal database

../_images/DB-Variant_1-Local_internal_database_720.png

This is the default database configuration and provides complete management of all database storage and management. No extra APIs or CLI parameters are required.

Configuration properties:

  • Tree-based data structures in memory for run-time transaction processing

  • This is the canonical database

  • There is no other database

  • The non-volatile load and store is done by netconfd-pro using the --startup filespec to store the configuration

  • The file is encoded as an XML instance document containing 1 element named 'data'

  • No YANG default leafs are stored in this file

Local internal database + NV-store Hook

../_images/DB-Variant_2-Local_internal_database+NV-store-hook_720.png

This database variant allows the vendor/system code to manage the non-volatile configuration. The system control is constrained to the replacement of the functions that load and store the configuration file.

This variant requires use of the NV-store APIs (described in the Database Transaction Callbacks section of this document).

Configuration properties:

  • Tree-based data structures in memory for run-time transaction processing

  • This is the canonical database

  • There is no other database

  • The non-volatile load and store is done by netconfd-pro via user callback functions.

  • The NV-store callback functions manage the actual non-volatile representation and storage.

  • The NV-Store callback transfers the configuration to/from netconfd-pro as an XML file

  • YANG default leafs will be treated as explicitly set if contained in the transfer file

Local internal database + no-NV-store

../_images/DB-Variant_3-Local_internal_database+no-NV-store_720.png
  • This variant allows either a simple deployment that does not support non-volatile storage of configuration or a more flexible system-managed non-volatile storage design, using multiple server APIs.

  • This variant requires that the CLI parameter --no-nvstore be set to 'false'.

  • The --startup CLI parameter is ignored if --no-nvstore is used.

  • Various initialization callback APIs and transaction APIs can be used to load and save the non-volatile storage

Configuration properties:

  • Tree-based data structures in memory for run-time transaction processing

  • This is the canonical database

  • There is no other database

  • The non-volatile load and store is done by netconfd-pro via various user callback functions.

  • The internal NV-store and NV-store callback functions are not used

  • The 'yp-system' library or individual SIL libraries can be used to initialize and fill the empty database with configuration and operational data nodes

  • Transaction hooks can be used to save the running configuration at run-time if any client edit transactions are processed by the server

Local internal database + External Edits

../_images/DB-Variant_4-Local_internal_database+External_Edits_720.png

This database variant allows an external process to initiate database edits, that are processed by the server as user or system edits.

The DB-API functions described in the next section can be used by the subsystem to edit the server configuration database.

This variant can be used with other variants for processing non-volatile storage. This variant is used to co-exist with a legacy or canonical database owned by the system.

Configuration properties:

  • Tree-based data structures in memory for run-time transaction processing

  • This may or may not be the canonical database, depending on the non-volatile storage options.

  • There may be another database in the system

  • The server uses its copy in memory as the complete database

  • The DB-API “send_edit” and “send_edit_ex” API functions can be used to transfer configuration from the system to netconfd-pro.

  • Data is transferred in XML

  • Error responses are sent by the server if an edit is not accepted.

  • An error will be returned if a client is already in the process of editing the database

  • The db-api-app application is installed in /usr/share/yumapro/src/db-api-app by default, and includes some examples of initializing the DB-API service and sending edits to the server.

Startup Options

There are 3 startup parameter variants, using the YANG 'start' choice statement.

The --startup=filespec CLI parameter can be used to specify the NV-storage file. The default filespec is $HOME/.yumapro/startup-cfg.xml.

The --factory-startup parameter can be used to load an empty configuration instead of the stored configuration. If the default configuration is used (startup-cfg.xml) then it is reset to factory default values.

The --startup-factory-file parameter can be used to load an non-empty configuration instead of the stored configuration. This file is used instead of the default configuration (startup-cfg.xml). It is ignored unless --factory-startup is also used.

The --no-startup parameter can be used to load an empty configuration instead of the stored configuration. The default configuration (startup-cfg.xml) is not affected by this parameter.

Server Source Code Generation

SIL Variants

Server instrumentation libraries can be local or remote:

  • SIL: synchronous local server instrumentation library within the netconfd-pro process via callback functions

  • SIL-SA: asynchronous remote sub-agent server instrumentation library, within another process, connected via YControl messages

Server instrumentation libraries can be for one module or a set of modules (called a bundle):

  • module: library is for one module. OK to use when there no "augment" modules needed within the same SIL or SIL-SA library

  • bundle: library is for a bundle of modules. OK to use when there is a base module and additional modules that augment the base module.

    • It is strongly recommended that a bundle be used for a base module and the modules that augment that base module.

    • This allows the callback code for augmenting modules to be generated correctly by yangdump-pro.

    • Any modules can be included a bundle, not just a base modules and modules that augment it.

make_sil Scripts

There are 4 scripts that can be used to generate SIL or SIL-SA code stubs.

Script

Purpose

make_sil_dir_pro

Generate C code stubs for a combined or split SIL variant

make_sil_sa_dir

Generate C code stubs for a combined or split SIL-SA variant

make_sil_bundle

Generate C code stubs for a split SIL bundle variant

make_sil_sa_bundle

Generate C code stubs for a split SIL-SA bundle variant

The SIL code for a YANG module can be generated with the make_sil_dir_pro script described in the next section.

A split SIL module for 'foo.yang' would be generated using the following files:

make_sil_dir foo

u_foo.c

User SIL

User-provided server instrumentation code for the 'foo' module.

u_foo.h

User SIL

User-provided external definitions for the 'foo' module. Should not edit!

y_foo.c

YumaPro SIL

YumaPro server glue code for the 'foo' module. Do not edit!

y_foo.h

YumaPro SIL

YumaPro server external definitions for the 'foo' module. Do not edit!

Combined SILs are considered deprecated, but still supported. A combined SIL module for 'foo.yang' would be generated using the following files:

make_sil_dir --no-split foo

foo.c

Combined YumaPro and User SIL

User-provided server instrumentation code for the 'foo' module.

foo.h

Combined YumaPro and User SIL

User-provided external definitions for the 'foo' module. Should not edit!

Note

SIL bundles always use "split mode" code generation.

A SIL bundle named bundle for 'test1.yang' and 'test2.yang' would be generated using the following files:

make_sil_bundle bundle1 test1 test2

y_bundle1.c

Bundle Wrapper

Bundle init and cleanup C code; Do not edit!

y_bundle1.h

Bundle Wrapper

Bundle init and cleanup H code; Do not edit!

u_test1.c

User SIL

User-provided server instrumentation code for the 'test1' module.

u_test1.h

User SIL

User-provided external definitions for the 'test1' module. Should not edit!

y_test1.c

YumaPro SIL

YumaPro server glue code for the 'test1' module. Do not edit!

y_test1.h

YumaPro SIL

YumaPro server external definitions for the 'test1' module. Do not edit!

u_test2.c

User SIL

User-provided server instrumentation code for the 'test2' module.

u_test2.h

User SIL

User-provided external definitions for the 'test2' module. Should not edit!

y_test2.c

YumaPro SIL

YumaPro server glue code for the 'test2' module. Do not edit!

y_test2.h

YumaPro SIL

YumaPro server external definitions for the 'test2' module. Do not edit!

Basic Development Steps

The steps needed to create server instrumentation for use within YumaPro are as follows:

  • Create the YANG module data model definition, or use an existing YANG module.

    • Validate the YANG module with the yangdump-pro program and make sure it does not contain any errors. All warnings should also be examined to determine if they indicate data modeling bugs or not.

      Example toaster.yang

  • Make sure the $YUMAPRO_HOME environment variable is defined, and pointing to your YumaPro development tree.

  • Create a SIL development subtree

    • Generate the directory structure and the Makefile with the make_sil_dir_pro script, installed in the /usr/bin directory. This step will also call yangdump-pro to generate the initial H and C files file the SIL.

      Example:

      mydir> make_sil_dir_pro test
      
  • Use your text editor to fill in the device-specific instrumentation for each object, RPC method, and notification. (In this example, edit test/src/u_test.c) Almost all possible NETCONF-specific code is either handled in the central stack, or generated automatically. so this code is responsible for implementing the semantics of the YANG data model.

  • Compile the SIL code

    • Use the 'make' command in the SIL 'src' directory. This should generate a library file in the SIL 'lib' directory.

      Example:

      mydir/test/src> make
      
  • Install the SIL library so it is available to the netconfd-pro server.

    • Use the 'make install' command in the SIL 'src' directory.

      Example:

      mydir/test/src> sudo make install
      
  • Run the netconfd-pro server (or build it again if linking with static libraries)

  • Load the new module

    • Be sure to add a 'load' command to the configuration file if the module should be loaded upon each reboot.

      yangcli-pro session:

      user@server1> load test
      
  • The netconfd-pro server will load the specified YANG module and the SIL and make it available to all sessions.

Add a SIL or SIL-SA Library to the Server

After the SIL or SIL-SA code is built and installed, it must be added to the server configuration. The mandatory information includes:

  • module or bundle name

  • enabled or disabled features

Configure the Library Name

The library name is the bundle name for a bundle and the module name for a module.

The server must be configured somehow to know which modules and bundles to load and support at runtime. The --module and --bundle CLI parameters are used for this purpose. The --loadpath CLI parameter can also be used to select modules to load at boot-time.

Configure the YANG Features

The server must also be configured so any YANG features are configured to match the implementation.

Note

If there are YANG features that are not supported in the SIL or SIL-SA instrumentation, then the server MUST be configured with the proper parameters to match the implementation.

The default server configuration will enable all YANG features. If not all YANG features are supported then the features must be configured.

There are two approaches supported:

These parameters are usually stored in the configuration file (default /etc/yumapro/netconfd-pro.conf) but they can also be specified in the command line invoking the server.

Development Environment

This section describes the YumaPro Tools development environment used to produce the Linux binaries.

Programs and Libraries Needed

There are several components used in the YumaPro software development environment:

  • gcc compiler and linker

  • ldconfig and install programs

  • GNU make program

  • shell program, such as bash

  • YumaPro development tree: the source tree containing YumaPro code, specified with the $YUMAPRO_HOME environment variable.

  • SIL development tree: the source tree containing server instrumentation code

The following external program is used by YumaPro, and needs to be pre-installed:

  • opensshd (needed by netconfd-pro)

    • The SSH2 server code does not link with netconfd-pro. Instead, the netconf-subsystem-pro program is invoked, and local connections are made to the netconfd-pro server from this SSH2 subsystem.

The following program is part of YumaPro Tools, and needs to be installed:

  • netconf-subsystem-pro (needed by netconfd-pro)

    • The thin client sub-program that is called by 'sshd' when a new SSH2 connection to the 'netconf' sub-system is attempted.

      • This program will use an AF_LOCAL socket, using a proprietary <ncx-connect> message, to communicate with the netconfd-pro server..

      • After establishing a connection with the netconfd-pro server, this program simply transfers SSH2 NETCONF channel data between sshd and netconfd-pro.

The following program is part of YumaPro Tools, and usually found within the YumaPro development tree:

  • netconfd-pro

    • The NETCONF server that processes all protocol operations.

      • The 'agt_ncxserver' component will listen for <ncx-connect> messages on the designated socket (/tmp/ncxserver.sock). If an invalid message is received, the connection will be dropped. Otherwise, the netconf-subsystem-pro program will begin passing NETCONF channel data to the netconfd-pro server. The first message is expected to be a valid NETCONF <hello> PDU. If

The following external libraries are used by YumaPro, and need to be pre-installed:

  • ncurses (needed by yangcli-pro)

    • character processing library needed by 'libtecla'; used within yangcli-pro

  • libc (or glibc, needed by all applications)

    • unix system library

  • libssh2 (needed by yangcli-pro)

    • SSH2 client library, used by yangcli-pro

  • libxml2 (needed by all applications)

    • xmlTextReader XML parser

    • pattern support

SIL and SIL-SA Overview

The SIL and SIL-SA callback functions are normally built as shared libraries that are loaded dynamically and accessed by the server with the 'dlopen' and 'dlsym' library functions.

They can also be linked statically at build time. A SIL-SA library can also be built as a static library and linked to the “sil-sa-app” or “combo-app” programs.

  • The user callback functions and their source files use the “u_” prefix.

  • YumaPro callback functions and their files use the “y_” prefix.

  • SIL callback code is executed in the netconfd-pro process.

  • SIL-SA callback code is executed in a different (possibly remote) process.

The following YANG statements are supported in SIL and SIL-SA code:

  • <rpc> statement

  • <notification> statement

  • Any data definition statement

  • <deviation> statement

The following YANG statement is supported in SIL or SIL-SA code if built into a bundle:

  • <augment> statement for external module

../_images/sil_file_flow.png

Note

  • SIL libraries cannot be generated for YANG submodules.

  • Only full modules are supported in SIL and SIL-SA automatic code generation.

SIL Library Names

SIL libraries must be named with a predictable string in order for the server to find it with minimal configuration.

The name must be “lib” + <library-name> + “.” + <file-ext>

where:

  • <library-name> is the YANG module name or bundle name

  • <file-ext> is the shared library file extension (usually “so”)

Example:

test.yang → test.c → libtest.so

SIL-SA Library Names

SIL libraries must be named with a predictable string in order for the server to find it with minimal configuration.

The name must be “lib” + <library-name> + “_sa” + “.” + <file-ext>

where:

  • <library-name> is the YANG module name or bundle name

  • <file-ext> is the shared library file extension (usually “so”)

Example:

test.yang → test.c → libtest_sa.so

SIL and SIL-SA Library Location

By default, SIL and SIL-SA binaries are installed in the /usr/lib/yumapro directory (/usr/lib64/yumapro on Fedora x64 platforms).

The $YUMAPRO_RUNPATH environment variable (or --runpath CLI parameter) is used to specify alternate locations for SIL library binary files.

Building SIL or SIL-SA Libraries

The compiler flags passed to “make” are extremely important when building SIL source code. The flags must align with those used to build the main server.

Most importantly, the “yumapro-pthreads” server must use SIL code that is compiled with the 'PTHREADS=1' compiler flag.** This flag is used in some H files and therefore data structures will be misaligned if the same value is not used in both the server and the SIL libraries.

The version string for a PTHREADS server build contains the phrase “THD” at the end. The command “netconfd-pro --version” can be used to print the server version string.

The DEBUG=1 flag can also be used when building SIL code. This will cause the --ggdb compiler flag to be used.

Extra yangdump-pro Parameters

The "make_sil_" scripts will pass extra parameters to the yangdump-pro program to affect code generation.

The following parameters are normally added automatically by these scripts:

  • --sil-edit2:

    • Use the second generation 'EDIT2' functions.

    • Now the default so this parameter can be omitted.

  • --sil-get2

    • Use the second generation 'GET2' functions.

    • Now the default so this parameter can be omitted.

The following parameters must be used to override these defaults:

  • --sil-edit1:

    • Use the first generation 'EDIT1 functions.

    • Now deprecated so do not use.

    • Will override the --sil-edit2 default.

  • --sil-get1:

    • Use the first generation 'GET1 functions.

    • Now deprecated so do not use.

    • Will override the --sil-get2 default.

The following parameter must be used to enable NMDA GET2 function generation:

  • --sil-nmda

    • Use the second generation 'GET2' functions.

    • There will be extra GET2 callbacks generated for config=true data nodes.

The following parameter can be used to alter code generation:

  • --sil-include=filename

    • The name of an include file to inject into C files

    • An include statement will be generated for each instance of this parameter, in the order these parameters are given.

    • Zero or more instances of this parameter are allowed.

    • The include statements will be generated after the system <include> statement and general YumaPro include statements, but before the YANG module specific include statements.

If any YANG deviations are used, then these files should be specified in the code generation parameters:

Warning

If any YANG data types are altered or replaced with deviations then the --deviation parameter must be used or improper code could be generated, possibly causing a server crash.

Refer to the Server SIL and SIL-SA Code Generation section for more details.

Using SIL and SIL-SA libraries with netconfd-pro

Static Configuration

Libraries for a single module are loaded at boot-time into the server with the --module parameter. This parameter applies to both SIL and SIL-SA libraries.

Libraries for a bundle are loaded at boot-time into the server with the --bundle parameter. This parameter applies to both SIL and SIL-SA libraries.

Note

  • If SIL-SA bundles are used, the server will not know the contents until a subsystem registers for the bundle.

  • This will prevent client access to all datastores until completed.

  • See the --wait-datastore-ready parameter for details.

Dynamic Configuration

Libraries for a single module are loaded at run-time into the server with the <load> operation and removed with the <unload> operation.

Libraries for a bundle are loaded at run-time into the server with the <load-bundle> operation and removed with the <unload-bundle> operation.

make_sil_dir_pro

The 'make_sil_dir_pro' script can be used to generate SIL code stub files for a single YANG module, for instrumentation running in the netconfd-pro process.

  • The script will generate a directory sub-tree and fill in the Makefiles.

  • The requested C and H file code stub files for a single YANG module will be generated in the "src" directory.

  • This script uses the yangdump-pro program to generate these files.

Note

In this mode, SIL code will be generated only for the definitions in the target module. External augment statements from the target module (or any other module) will not affect SIL code generation.

../_images/make_sil_dir_files.png

make_sil_dir_pro Parameters

make_sil_dir_pro [--no-split | --split] module-spec 0*(extra-parms)

Example:

> make_sil_dir_pro address
  • --split

    • Generate separate user and system code stubs. Now the default so this parameter can be omitted.

  • --no-split

    • Generate combined code stubs. This mode is deprecated.

  • module-spec

    • module name to pick the module revision based on the search path

      make_sil_dir_pro ietf-ip
      
    • module name plus revision data to pick a specific module revision

      make_sil_dir_pro ietf-ip@2014-06-16
      
  • extra-parms

make_sil_bundle

The 'make_sil_bundle' script can be used to generate SIL code stub files for multiple YANG modules.

  • The parameters are a list of strings.

  • The first parameter is the bundle name.

  • Any additional "plain string" parameters are module specifications. These module names should be listed so the base modules are specified first and modules that augment the base modules are listed last.

  • Strings beginning with -- will be passed to yangdump-pro as parameters

  • The script will generate a directory sub-tree and fill in the Makefiles and source files.

  • SIL code will be generated for the definitions in the target module.

  • External augment statements from all the modules loaded together will affect SIL code generation.

make_sil_bundle Parameters

make_sil_bundle  bundle-name 1*(module-spec) 0*(extra-parms)

Example:

> make_sil_bundle ifbundle ietf-interfaces iana-if-type ietf-ip

In this example, a bundle named 'ifbundle' is created. The same directory structure is used as for a single module. C files are created for the bundle as well as the module files.

  • bundle-name

    • Name of the bundle.

    • Must not conflict with any module name.

  • module-spec

    • module name to pick the module revision based on the search path

    • module name plus revision data to pick a specific module revision

  • extra-parms

make_sil_sa_dir

The 'make_sil_sa_dir' script can be used to generate the C and H file code stubs for a single YANG module.

  • The script will generate a directory sub-tree and fill in the Makefiles.

  • The requested C and H file code stub files for a single YANG module will be generated in the "src" directory.

  • This script uses the yangdump-pro program to generate these files.

  • The --sil-get1 parameter is not allowed for SIL-SA code

Note

In this mode, SIL code will be generated only for the definitions in the target module. External augment statements from the target module (or any other module) will not affect SIL code generation.

make_sil_sa_dir Parameters

make_sil_sa_dir [--no-split | --split] module-spec 0*(extra-parms)

Example:

> make_sil_sa_dir address
  • --split

    • Generate separate user and system code stubs. Now the default so this parameter can be omitted.

  • --no-split

    • Generate combined code stubs. This mode is deprecated.

  • module-spec

    • module name to pick the module revision based on the search path

    • module name plus revision data to pick a specific module revision

  • extra-parms

make_sil_sa_bundle

The 'make_sil_sa_bundle' script can be used to generate the C and H file code stubs for a set of YANG modules.

The same YANG usage restrictions for SIL bundles apply to SIL-SA bundles. Refer to the make_sil_bundle section for details.

../_images/make_sil_sa_bundle.png

make_sil_sa_bundle Parameters

make_sil_sa_bundle  bundle-name 1*(module-spec) 0*(extra-parms)

Example:

> make_sil_sa_bundle ifbundle ietf-interfaces iana-if-type ietf-ip

In this example, a bundle named 'ifbundle' is created. The same directory structure is used as for a single module. C files are created for the bundle as well as the module files.

  • bundle-name

    • Name of the bundle.

    • Must not conflict with any module name.

  • module-spec

    • module name to pick the module revision based on the search path

    • module name plus revision data to pick a specific module revision

  • extra-parms

Static SIL-SA Libraries

The SIL-SA library for a module or a bundle can be built as a static library and linked directly into the “sil-sa-app” or “combo-app” programs (or an application based on these programs).

Quick Steps

  1. make_sil_sa_dir or make_sil_sa_bundle

  2. make STATIC=1

  3. register static library in the SIL-SA application

There is no limit to the number of static SIL-SA libraries that can be linked into the program image or registered with the SIL-SA subsystem.

A static SIL-SA library is not used unless it is configured for use in the main server. The code is available at all times (since it is statically linked) and cannot be removed (only disabled).

Enable a static SIL-SA library

  • Configuration parameter

    • --module=<module-name>

    • --bundle=<bundle-name>

  • RPC operation

    • <load>

    • <load-bundle>

Disable a static SIL-SA library

  • RPC operation

    • <unload>

    • <unload-bundle>

Static SIL-SA Library Example

This information is also in the README-STATIC_SILSA.txt file in the root of the source code directory.

IMPORTANT info on Using Static SIL-SA Libraries

  1. Assume a SIL-SA library is setup

    > make_sil_sa_dir test2
    

    This could also be a bundle (make_sil_sa_bundle)

  2. Build a static version of the SIL-SA library

    > cd test2
    
    > make STATIC=1
    
  3. Copy the static library to a common directory if desired.

    This is not required but may make using the STATIC_SILSA variable easier.

    > cp lib/libtest2.a $HOME/silsa/libtest2_sa.a
    
  4. Use the macro STATIC_SILSA when building the sil-sa-app

    Option 1: Change in Makefile:

    STATIC_SILSA=-L /home/andy/silsa -ltest2_sa
    

    Option 2: set from command line:

    > STATIC_SILSA='-L /home/andy/silsa -ltest2_sa' make
    

Note

The -L part must be first and it must specify where the static libraries are located. One or more -l parts can follow.

The -l parameter does not use the full name. Instead libtest2_sa.a can be specified as -ltest2_sa.

  1. Register the static library in the sil-sa-app or combo-app program

    Example from sil-sa-app/main.c:

    /* extern definitions for the 3 expected callbacks */
    AGT_SIL_LIB_EXTERN(test2)
    
    static status_t static_silsa_init (void)
    {
        /* example: module=test2;
         * need to use in Makefile (example)
         * STATIC_SILSA=-L /home/andy/silsa -ltest2_sa
         * The actual library names are not needed in this code
         */
        status_t res =
            agt_sil_lib_register_statlib((const xmlChar *)"test2",
                                         y_test2_init,
                                         y_test2_init2,
                                         y_test2_cleanup);
    
        return res;
    }
    
  2. The SIL-SA library will not be used unless the module or bundle is loaded.

    # <load> at runtime with yangcli-pro
    > load test2
    
    # load at boot-time in server
    > netconfd-pro module=test2
    

SIL Makefile

The SIL-SA Makefile is based on the Makefile for YumaPro sources. The automake program is not used at this time. There is no ./configure script to run. There are 2 basic build steps:

  1. make [DEBUG=1] [STATIC=1]

  2. [sudo] make install

The installation step may require root access via the 'sudo' program, depending on the system.

Target Platforms

The following target platforms are supported:

  • Fedora, Ubuntu, CentOS: These are the default targets and no special command line options are needed.

Build Targets

The following table describes the build targets that are supported from the top-level SIL directory. More targets are available in the 'src' directory.

YumaPro Build Targets

Target

Description

all

Make library binaries

clean

Remove the target library binary files

distclean

Remove all the dependencies and all target files

doc

Make the Doxygen files

install

Install the library components into the system.

superclean

Remove all the dependencies and the library binaries

uninstall

Remove the library components from the system.

Command Line Build Options

There are several command line options that can be used when making the YumaPro source code. These may have an impact on building and linking the SIL source code. The top-level README file in the source code contains the latest definition of the make flags.

Example SIL Makefile

The script make_sil_dir_pro (installed as /usr/bin/make_sil_dir_pro) or make_sil_bundle (installed as /usr/bin/make_sil_bundle) is used to automatically create a SIL work sub-directory tree. Support files are located in the /usr/share/yumapro/util directory.

The Makefiles for SIL and SIL-SA modules are located in the “util” directory

  • Default SIL Makefile location: /usr/share/yumapro/util

Short Names for SIL and SIL-SA Code Generation

Starting in release 20.10-5 the --short-names parameter is available to control long or short name generation for SIL and SIL-SA code.

  • long name: contains the module name and the complete path name of the object in the name.

    • This generates a completely unique function or identifier name that cannot conflict within the module or with any other module. However, the names can be very long and difficult to read or use in C code.

  • short name: the module prefix or --force-prefix parameter value is used instead of the module name.

    • The object name is used instead of the path component.

    • If the name is already used then a suffix such as “_1” will be added to the name so that it is unique.

    • A short name is a match if the prefix part and the path part are the same strings.

    • A short name is unique within the module or bundle, and should be globally unique if the module prefixes are unique.

    • A short name can change if the code is regenerated and the module has changed so that duplicate names have been added.

    • E.g., name might change to name_1 and the old name_1 might change to name_2.

Example:

Path: leaf /interfaces/interface/admin-status

GET2 long function:

u_ietf_interfaces_interfaces_interface_admin_status_get

GET2 short function:

u_if_admin_status_get

CLI Parameters For Short Names

There are two yangdump-sdk (or make_sil* script) CLI parameters that control the short name generation

  • --short-names: Selects short names or long names

  • --force-prefix: Override the module prefix in short name generation. Ignored if --short-names=false.

Short Name Summary

If the --short-names parameter is set to “true” (the default value) then short names will be used.

The header for each C file will contain the Short Name Mapping used.

SIL Example:

> make_sil_dir_pro ietf-interfaces

Example Short Name Summary from u_ietf-interfaces.c

Short Name Mappings

  Short Name Mappings
    admin_status = /interfaces/interface/admin-status
    admin_status_1 = /interfaces-state/interface/admin-status
    description = /interfaces/interface/description
    discontinuity_time = /interfaces/interface/statistics/discontinuity-time
    discontinuity_time_1 = /interfaces-state/interface/statistics/discontinuity-time
    enabled = /interfaces/interface/enabled
    higher_layer_if = /interfaces/interface/higher-layer-if
    higher_layer_if_1 = /interfaces-state/interface/higher-layer-if
    if_index = /interfaces/interface/if-index
    if_index_1 = /interfaces-state/interface/if-index
    in_broadcast_pkts = /interfaces/interface/statistics/in-broadcast-pkts
    in_broadcast_pkts_1 = /interfaces-state/interface/statistics/in-broadcast-pkts
    in_discards = /interfaces/interface/statistics/in-discards
    in_discards_1 = /interfaces-state/interface/statistics/in-discards
    in_errors = /interfaces/interface/statistics/in-errors
    in_errors_1 = /interfaces-state/interface/statistics/in-errors
    in_multicast_pkts = /interfaces/interface/statistics/in-multicast-pkts
    in_multicast_pkts_1 = /interfaces-state/interface/statistics/in-multicast-pkts
    in_octets = /interfaces/interface/statistics/in-octets
    in_octets_1 = /interfaces-state/interface/statistics/in-octets
    in_unicast_pkts = /interfaces/interface/statistics/in-unicast-pkts
    in_unicast_pkts_1 = /interfaces-state/interface/statistics/in-unicast-pkts
    in_unknown_protos = /interfaces/interface/statistics/in-unknown-protos
    in_unknown_protos_1 = /interfaces-state/interface/statistics/in-unknown-protos
    interface = /interfaces/interface
    interface_1 = /interfaces-state/interface
    interfaces = /interfaces
    interfaces_state = /interfaces-state
    last_change = /interfaces/interface/last-change
    last_change_1 = /interfaces-state/interface/last-change
    link_up_down_trap_enable = /interfaces/interface/link-up-down-trap-enable
    lower_layer_if = /interfaces/interface/lower-layer-if
    lower_layer_if_1 = /interfaces-state/interface/lower-layer-if
    name = /interfaces/interface/name
    name_1 = /interfaces-state/interface/name
    oper_status = /interfaces/interface/oper-status
    oper_status_1 = /interfaces-state/interface/oper-status
    out_broadcast_pkts = /interfaces/interface/statistics/out-broadcast-pkts
    out_broadcast_pkts_1 = /interfaces-state/interface/statistics/out-broadcast-pkts
    out_discards = /interfaces/interface/statistics/out-discards
    out_discards_1 = /interfaces-state/interface/statistics/out-discards
    out_errors = /interfaces/interface/statistics/out-errors
    out_errors_1 = /interfaces-state/interface/statistics/out-errors
    out_multicast_pkts = /interfaces/interface/statistics/out-multicast-pkts
    out_multicast_pkts_1 = /interfaces-state/interface/statistics/out-multicast-pkts
    out_octets = /interfaces/interface/statistics/out-octets
    out_octets_1 = /interfaces-state/interface/statistics/out-octets
    out_unicast_pkts = /interfaces/interface/statistics/out-unicast-pkts
    out_unicast_pkts_1 = /interfaces-state/interface/statistics/out-unicast-pkts
    phys_address = /interfaces/interface/phys-address
    phys_address_1 = /interfaces-state/interface/phys-address
    speed = /interfaces/interface/speed
    speed_1 = /interfaces-state/interface/speed
    statistics = /interfaces/interface/statistics
    statistics_1 = /interfaces-state/interface/statistics
    type = /interfaces/interface/type
    type_1 = /interfaces-state/interface/type

Short Names for Constants

The constants generated in a SIL or SIL-SA H file will be affected by the short-names parameter.

Examples

Module and Revision Constants

Long name constant:

#define y_ietf_interfaces_M_ietf_interfaces (const xmlChar *)"ietf-interfaces"
#define y_ietf_interfaces_R_ietf_interfaces (const xmlChar *)"2018-02-20"

Short name constant:

#define y_if_M_if (const xmlChar *)"ietf-interfaces"
#define y_if_R_if (const xmlChar *)"2018-02-20"
Feature Constants

Long name constant:

#define u_ietf_interfaces_F_arbitrary_names 1

Short name constant:

#define u_if_F_arbitrary_names 1
Data Structure Typedef Constants

Long name constant:

/* leaf-list /interfaces/interface/lower-layer-if */
typedef struct y_ietf_interfaces_T_interfaces_interface_lower_layer_if_ {
    dlq_hdr_t qhdr;
    xmlChar *v_lower_layer_if;
} y_ietf_interfaces_T_interfaces_interface_lower_layer_if;

Short name constant:

typedef struct y_if_T_lower_layer_if_ {
    dlq_hdr_t qhdr;
    xmlChar *v_lower_layer_if;
} y_if_T_lower_layer_if;
Object Name Constant

Long name constant:

#define y_ietf_interfaces_N_admin_status (const xmlChar*)"admin-status"

Short name constant:

#define y_if_N_admin_status (const xmlChar *)"admin-status"

Function and Variable Names

The function and variable names generated in a SIL or SIL-SA H file will be affected by the short-names parameter.

GET2 Example

Long name:

extern status_t u_ietf_interfaces_interfaces_interface_admin_status_get (
    getcb_get2_t *get2cb,
    const xmlChar *k_interfaces_interface_name);

Short name:

extern status_t u_if_admin_status_get (
    getcb_get2_t *get2cb,
    const xmlChar *k_name);
EDIT2 Example

Long name:

extern status_t u_ietf_interfaces_interfaces_interface_edit (
    ses_cb_t *scb,
    rpc_msg_t *msg,
    agt_cbtyp_t cbtyp,
    op_editop_t editop,
    val_value_t *newval,
    val_value_t *curval,
    const xmlChar *k_interfaces_interface_name);

Short name:

extern status_t u_if_interface_edit (
    ses_cb_t *scb,
    rpc_msg_t *msg,
    agt_cbtyp_t cbtyp,
    op_editop_t editop,
    val_value_t *newval,
    val_value_t *curval,
    const xmlChar *k_name);

SIL External Interface

Each SIL has 2 initialization functions and 1 cleanup function that must be present.

  • The first initialization callback function is used to set up the configuration related objects.

  • The second initialization callback is used to setup up non-configuration objects, after the running configuration has been loaded from the startup file.

  • The cleanup callback is used to remove all SIL data structures and unregister all callback functions.

These are the only SIL functions that the server will invoke directly. They are generated by yangdump-pro with the --format parameter, and usually do not require any editing by the developer.

Most of the work done by SIL code is through callback functions for specific RPC operations and database objects. These callback functions are usually registered during the initialization functions.

The following YANG module will be used for the examples in this section. It is installed as /usr/share/yumapro/modules/test/pass/address.yang

module address {
  namespace "http://www.yumaworks.com/ns/address";
  prefix addr;

  revision "2016-01-22";

  container addresses {
    list address {
      key "last-name first-name";
      leaf last-name {
        type string;
        description
          "Last name of the person who is associated with this address";
      }
      leaf first-name {
        type string;
        description
          "First name of the person who is associated with this address";
      }
      leaf street {
        type string;
        description "street address";
      }
      leaf city {
        type string;
        description "City address";
      }
      leaf zipcode {
        type string { length "5 | 10"; }
        description "zipcode";
      }
      list phone {
        key phone-type;
        leaf phone-type {
          type enumeration {
            enum cell;
            enum home;
            enum work;
          }
        }
        leaf phone-number {
          mandatory true;
          type string;
        }
      }  // list phone
    } // list address
  } // container addresses

} // module address

Stage 1 Initialization

The stage 1 initialization function is the first function called in the library by the server.

This function MUST NOT attempt to access any database. The configuration databases are not ready for access at this time. Use the 'init2' function to adjust the running configuration.

This callback function is expected to perform the following functions:

  • initialize any module static data

  • make sure the requested module name and optional revision date parameters are correct

  • load the requested module name and revision with ncxmod_load_module

  • setup top-level object cache pointers (if needed)

  • register any RPC method callbacks with agt_rpc_register_method

  • register any database object callbacks with agt_cb_register_callback

  • perform any device-specific and/or module-specific initialization

INIT1 Callback Template

Name Format:

y_<modname>_init

Input:

  • modname == string containing module name to load

  • revision == string containing revision date to use. Set to NULL if the operator did not specify a revision.

Returns:

  • operation status (0 if success)

The SIL phase 1 init function uses the following callback template:

typedef status_t (*agt_sil_init_fn_t)(const xmlChar *modname, const xmlChar *revision)

SIL init function template.

Usually loads the module(s) and registers the callbacks

Param modname

requested module name to load

Param revision

requested revision date of module to load

Return

stats

System INIT1 Callback

Example INIT1 function header generated by yangdump-sdk for a system file:

/**
 * @brief Phase 1: Initialize the address server instrumentation library.
 *
 * Called by server when module is loaded.
 *
 * @param modname requested module name to load
 * @param revision requested revision date of the module to load.
 * This may be NULL if the module has no revision statements.
 * @return return status. An error will cause the module load to fail.
 */
extern status_t y_address_init (
    const xmlChar *modname,
    const xmlChar *revision);

Example INIT1 function generated by yangdump-sdk for a system file:

This is a system SIL file representing the mandatory external interface.

/**
 * @brief Phase 1: Initialize the address server instrumentation library.
 *
 * Called by server when module is loaded.
 *
 * @param modname requested module name to load
 * @param revision requested revision date of the module to load.
 * This may be NULL if the module has no revision statements.
 * @return return status. An error will cause the module load to fail.
 */
status_t y_address_init (
    const xmlChar *modname,
    const xmlChar *revision)
{
    status_t res = NO_ERR;

    if (LOGDEBUG) {
        log_debug("\nEnter y_address_init");
    }


    y_addr_init_static_vars();

    /* change if custom handling done */
    if (xml_strcmp(modname, y_addr_M_addr)) {
        return ERR_NCX_UNKNOWN_MODULE;
    }

    if (revision && xml_strcmp(revision, y_addr_R_addr)) {
        return ERR_NCX_WRONG_VERSION;
    }

    res = ncxmod_load_module(
        y_addr_M_addr,
        y_addr_R_addr,
        agt_get_savedevQ(),
        &address_mod);
    if (res != NO_ERR) {
        return res;
    }

    addresses_obj = ncx_find_object(
        address_mod,
        y_addr_N_addresses);
    if (addresses_obj == NULL) {
        return ERR_NCX_DEF_NOT_FOUND;
    }

    res = agt_cb_register_edit2_callback(
        y_addr_M_addr,
        (const xmlChar *)"/addr:addresses",
        y_addr_R_addr,
        addresses_edit);
    if (res != NO_ERR) {
        return res;
    }

    res = agt_cb_register_edit2_callback(
        y_addr_M_addr,
        (const xmlChar *)"/addr:addresses/addr:address",
        y_addr_R_addr,
        address_edit);
    if (res != NO_ERR) {
        return res;
    }

    res = agt_cb_register_edit2_callback(
        y_addr_M_addr,
        (const xmlChar *)"/addr:addresses/addr:address/addr:phone",
        y_addr_R_addr,
        phone_edit);
    if (res != NO_ERR) {
        return res;
    }

    res = u_address_init(modname, revision);
    return res;

} /* y_address_init */

User INIT1 Callback

If the "--split" parameter is used to generate the SIL or SIL-SA code stubs (now the default) then there will be separate "user" C files created (e.g., 'u_address.h' and 'u_address.c').

The platform-specific initialization code for this module should be placed in the "user" file, not the "system" file.

There will be code generated automatically to invoke the User INIT1 callback from the System INIT1 callback.

From This Example:

res = u_address_init(modname, revision);

This is user SIL file representing the optional external interface. You can change the "user" function templates, if desired. These functions are never called by the server directly.

Example INIT1 function header generated by yangdump-sdk for a user file:

/**
 * @brief Phase 1: Initialize the address server instrumentation library.
 *
 * Called by server when module is loaded.
 *
 * @param modname requested module name to load
 * @param revision requested revision date of the module to load.
 * This may be NULL if the module has no revision statements.
 * @return return status. An error will cause the module load to fail.
 */
extern status_t u_address_init (
    const xmlChar *modname,
    const xmlChar *revision);

Example INIT1 function generated by yangdump-sdk for a user file:

/**
 * @brief Phase 1: Initialize the address server instrumentation library.
 *
 * Called by server when module is loaded.
 *
 * @param modname requested module name to load
 * @param revision requested revision date of the module to load.
 * This may be NULL if the module has no revision statements.
 * @return return status. An error will cause the module load to fail.
 */
status_t u_address_init (
    const xmlChar *modname,
    const xmlChar *revision)
{
    status_t res = NO_ERR;

    if (LOGDEBUG) {
        log_debug("\nEnter u_address_init");
    }

    ncx_module_t *address_mod = NULL;

    address_mod = ncx_find_module(modname, revision);
    if (address_mod == NULL) {
        return ERR_NCX_OPERATION_FAILED;
    }

    /* put your module initialization code here */

    return res;

} /* u_address_init */

Stage 2 Initialization

The stage 2 initialization function is the second function called in the library by the server:

  • It will only be called if the stage 1 initialization is called first, and it returns 0 (NO_ERR status).

  • This function is used to initialize any needed data structures in the running configuration, such as factory default configuration, read-only counters and status objects.

  • It is called after the startup configuration has been loaded into the server.

  • If the <load> operation is used during server operation, then this function will be called immediately after the state 1 initialization function.

Note that configuration data structures that are loaded during server initialization will be handled by the database callback functions registered during phase 1 initialization.

Any server-created configuration nodes should be created during phase 2 initialization (this function), after examining the explicitly-provided configuration data. For example, the top-level /nacm container will be created (in agt/agt_acm.c) if it is not provided in the startup configuration.

This callback function is expected to perform the following functions:

  • load non-configuration data structures into the server (if needed)

  • initialize top-level data node cache pointers (if needed)

  • load factory-default configuration data structures into the server (if needed)

  • optionally save a cached pointer to a data tree node (such as the root node for the module). The 'agt_create_cache' function in agt/agt_util.h is used to initialize such a module-static variable.

INIT2 Callback Template

Name Format:

y_<modname>_init2

Returns:

  • operation status (0 if success)

The SIL phase 2 init function uses the following callback template:

typedef status_t (*agt_sil_init2_fn_t)(void)

SIL init2 function template.

Usually sets up post-startup-cfg configuration and monitoring

Return

status

System INIT2 Callback

Example INIT2 function header generated by yangdump-sdk for a system file:

/**
 * @brief Phase 2: Initialize the address server instrumentation library.
 *
 * SIL init phase 2: non-config data structures.
 * Called after running config is loaded.
 *
 * @return return status. An error will cause the
 * server initialization to fail.
 */
extern status_t y_address_init2 (void);

Example INIT2 function generated by yangdump-sdk for a system file:

/**
 * @brief Phase 2: Initialize the address server instrumentation library.
 *
 * SIL init phase 2: non-config data structures.
 * Called after running config is loaded.
 *
 * @return return status. An error will cause the
 * server initialization to fail.
 */
status_t y_address_init2 (void)
{
    status_t res = NO_ERR;

    if (LOGDEBUG) {
        log_debug("\nEnter y_address_init2");
    }


    addresses_val = agt_init_cache(
        y_addr_M_addr,
        y_addr_N_addresses,
        &res);
    if (res != NO_ERR) {
        return res;
    }

    res = u_address_init2();

    return res;

} /* y_address_init2 */

User INIT2 Callback

If the "--split" parameter was used to generate the SIL or SIL-SA code stubs, then there will be code generated automatically to invoke the User INIT2 callback from the System INIT2 callback.

From This Example:

res = u_address_init2();

This is user SIL file representing the optional external interface. You can change the "user" function templates, if desired. These functions are never called by the server directly.

Example INIT2 function header generated by yangdump-sdk for a user file:

/**
 * @brief Phase 2: Initialize the address server instrumentation library.
 *
 * SIL init phase 2: non-config data structures.
 * Called after running config is loaded.
 *
 * @return return status. An error will cause the
 * server initialization to fail.
 */
extern status_t u_address_init2 (void);

Example INIT2 function generated by yangdump-sdk for a user file:

/**
 * @brief Phase 2: Initialize the address server instrumentation library.
 *
 * SIL init phase 2: non-config data structures.
 * Called after running config is loaded.
 *
 * @return return status. An error will cause the
 * server initialization to fail.
 */
status_t u_address_init2 (void)
{
    status_t res = NO_ERR;

    if (LOGDEBUG) {
        log_debug("\nEnter u_address_init2");
    }


    /* put your init2 code here */

    return res;

} /* u_address_init2 */

Cleanup

The cleanup function is called during server shutdown. It is only called if the stage 1 initialization function is called. It will be called right away if either the stage 1 or stage 2 initialization functions return a non-zero error status.

This callback function is expected to perform the following functions:

  • cleanup any module static data

  • free any top-level object cache pointers (if needed)

  • unregister any RPC method callbacks with 'agt_rpc_unregister_method'

  • unregister any database object callbacks with 'agt_cb_unregister_callbacks'

  • perform any device-specific and/or module-specific cleanup

Cleanup Callback Template

Name Format:

y_<modname>_cleanup

The SIL cleanup function uses the following callback template:

typedef void (*agt_sil_cleanup_fn_t)(void)

SIL cleanup function template.

Usually unloads the module(s) and free any used memory

System Cleanup Callback

Example Cleanup function header generated by yangdump-sdk for a system file:

/**
 * @brief Cleanup the address server instrumentation library.
 *
 * Called by server when module is unloaded.
 *
 */
extern void y_address_cleanup (void);

Example Cleanup function generated by yangdump-sdk for a system file:

/**
 * @brief Cleanup the address server instrumentation library.
 *
 * Called by server when module is unloaded.
 *
 */
void y_address_cleanup (void)
{

    if (LOGDEBUG) {
        log_debug("\nEnter y_address_cleanup");
    }


    agt_cb_unregister_callbacks(
        y_addr_M_addr,
        (const xmlChar *)"/addr:addresses");

    agt_cb_unregister_callbacks(
        y_addr_M_addr,
        (const xmlChar *)"/addr:addresses/addr:address");

    agt_cb_unregister_callbacks(
        y_addr_M_addr,
        (const xmlChar *)"/addr:addresses/addr:address/addr:phone");
    u_address_cleanup();

} /* y_address_cleanup */

User Cleanup Callback

If the "--split" parameter was used to generate the SIL or SIL-SA code stubs, then there will be code generated automatically to invoke the User Cleanup callback from the System Cleanup callback.

From This Example:

res = u_address_cleanup();

This is user SIL file representing the optional external interface. You can change the "user" function templates, if desired. These functions are never called by the server directly.

Example Cleanup function header generated by yangdump-sdk for a user file:

/**
 * @brief Cleanup the address server instrumentation library.
 *
 * Called by server when module is unloaded.
 *
 */
extern void u_address_cleanup (void);

Example Cleanup function generated by yangdump-sdk for a user file:

/**
 * @brief Cleanup the address server instrumentation library.
 *
 * Called by server when module is unloaded.
 *
 */
void u_address_cleanup (void)
{

    if (LOGDEBUG) {
        log_debug("\nEnter u_address_cleanup");
    }


    /* put your cleanup code here */

} /* u_address_cleanup */

Error Handling

The server has several APIs and built-in mechanisms for generating error responses for client requests. This is done is a protocol-independent way. The error reporting procedures and data sre protocol-specific, but these are handled automatically by the server.

RPC Error Example

Assuming the “interfaces” container preexist in the datastore. To invoke the callback the following RPC can be sent. This example contains an error so a RESTCONF <errors> block will be sent to the client.

<rpc message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <edit-config>
    <target>
      <candidate/>
    </target>
    <config>
      <interfaces xmlns="http://yumaworks.com/ns/ietf-interfaces-example">
        <interface>
          <name>not-supported</name>
        </interface>
     </interfaces>
    </config>
  </edit-config>
</rpc>

The server may reply with the following:

<errors xmlns:ncx="http://netconfcentral.org/ns/yuma-ncx"
  xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf">
  <error>
    <error-type>rpc</error-type>
    <error-tag>operation-not-supported</error-tag>
    <error-app-tag>no-support</error-app-tag>
    <error-message xml:lang="en">operation not supported</error-message>
    <error-info>
      <error-number>273</error-number>
    </error-info>
  </error>
</errors>

RPC Error Message

NETCONF has several standard error fields that are returned in an <rpc-error> response. These are defined in RFC 6241, section 4.3.

RESTCONF defines its own structure to return for errors, but it has the same child fields as NETCONF. Only the container structure is different. These are defined in RFC 8040.

error-type

Mandatory field set by the server indicating the protocol layer where the error occurred.

One of:

  • transport

  • rpc

  • protocol

  • application

The 'ncx_layer_t' enumeration defined in ncx/ncxtypes.h is used for this field.

enum ncx_layer_t

Enumeration of NETCONF protocol layers.

Values:

enumerator NCX_LAYER_NONE

not set

enumerator NCX_LAYER_TRANSPORT

transport layer

enumerator NCX_LAYER_RPC

RPC operation layer.

enumerator NCX_LAYER_OPERATION

protocol operation layer

enumerator NCX_LAYER_CONTENT

application layer

error-tag

Mandatory enumeration for the type of error that occurred. This field is set by the server depending on the 'status_t' of the error. The values are defined in RFC 6241, Appendix A.

One of:

  • in-use

  • invalid-value

  • too-big

  • missing-attribute

  • bad-attribute

  • unknown-attribute

  • missing-element

  • bad-element

  • unknown-element

  • unknown-namespace

  • access-denied

  • lock-denied

  • resource-denied

  • rollback-failed

  • data-exists

  • data-missing

  • operation-not-supported

  • operation-failed

  • malformed-message

The 'rpc_err_t' enumeration defined in ncx/rpc_err.h is used for this field.

enum rpc_err_t

enumerations for NETCONF standard errors

Values:

enumerator RPC_ERR_NONE

not set

enumerator RPC_ERR_IN_USE

in-use

enumerator RPC_ERR_INVALID_VALUE

invalid-value

enumerator RPC_ERR_TOO_BIG

too-big

enumerator RPC_ERR_MISSING_ATTRIBUTE

missing-attribute

enumerator RPC_ERR_BAD_ATTRIBUTE

bad-attribute

enumerator RPC_ERR_UNKNOWN_ATTRIBUTE

unknown-attribute

enumerator RPC_ERR_MISSING_ELEMENT

missing-element

enumerator RPC_ERR_BAD_ELEMENT

bad-element

enumerator RPC_ERR_UNKNOWN_ELEMENT

unknown-element

enumerator RPC_ERR_UNKNOWN_NAMESPACE

unknown-namespace

enumerator RPC_ERR_ACCESS_DENIED

access-denied

enumerator RPC_ERR_LOCK_DENIED

lock-denied

enumerator RPC_ERR_RESOURCE_DENIED

resource-denied

enumerator RPC_ERR_ROLLBACK_FAILED

rollback-failed

enumerator RPC_ERR_DATA_EXISTS

data-exists

enumerator RPC_ERR_DATA_MISSING

data-missing

enumerator RPC_ERR_OPERATION_NOT_SUPPORTED

operation-not-supported

enumerator RPC_ERR_OPERATION_FAILED

operation-failed

enumerator RPC_ERR_PARTIAL_OPERATION

partial-operation, deprecated; not used

enumerator RPC_ERR_MALFORMED_MESSAGE

malformed-message

error-severity

Mandatory field set by the server. The value error is (almost) always used because no standard error-tag values are defined with a severity of warning.

One of:

  • error

  • warning

The 'rpc_err_sev_t' enumeration defined in ncx/rpc_err.h is used for this field.

enum rpc_err_sev_t

enumerations for NETCONF standard error severities

The protocol does not actually use RPC warnings and these are not supported or used in the server

Values:

enumerator RPC_ERR_SEV_NONE

not set

enumerator RPC_ERR_SEV_WARNING

warning

enumerator RPC_ERR_SEV_ERROR

error (only used value)

Note

The warning enumeration can be used even though it is not standard. The --with-warnings CLI parameter must be used to enable this feature in the server. If set then the agt_record_warning API function can be used to record a warning instead of an error.

error-app-tag

Optional field identifying the application-specific error. There are some standard values defined by YANG that must be used in certain error conditions.

The server will use these values even if error-app-tag values are defined for the specific errors instead:

  • must-violation

  • instance-required

  • missing-choice

  • missing-instance

Reserved NETCONF error-app-tag Values

Several values are defined in NETCONF standards. These cannot be redefined in the server.

#define RPC_ERR_APPTAG_UNIQUE_FAILED   (const xmlChar *)"data-not-unique"
#define RPC_ERR_APPTAG_MAX_ELEMS       (const xmlChar *)"too-many-elements"
#define RPC_ERR_APPTAG_MIN_ELEMS       (const xmlChar *)"too-few-elements"
#define RPC_ERR_APPTAG_MUST            (const xmlChar *)"must-violation"
#define RPC_ERR_APPTAG_INSTANCE_REQ    (const xmlChar *)"instance-required"
#define RPC_ERR_APPTAG_CHOICE          (const xmlChar *)"missing-choice"
#define RPC_ERR_APPTAG_INSERT          (const xmlChar *)"missing-instance"
#define RPC_ERR_APPTAG_RANGE           (const xmlChar *)"not-in-range"
#define RPC_ERR_APPTAG_VALUE_SET       (const xmlChar *)"not-in-value-set"
#define RPC_ERR_APPTAG_PATTERN         (const xmlChar *)"pattern-test-failed"
#define RPC_ERR_APPTAG_DATA_REST       (const xmlChar *)"data-restriction-violation"
#define RPC_ERR_APPTAG_DATA_NOT_UNIQUE (const xmlChar *)"data-not-unique"
#define RPC_ERR_APPTAG_NO_MATCHES      (const xmlChar *)"no-matches"
#define RPC_ERR_APPTAG_NOT_NODESET     (const xmlChar *)"not-a-node-set"
#define RPC_ERR_APPTAG_LOCKED          (const xmlChar *)"locked"
#define RPC_ERR_APPTAG_COMMIT          (const xmlChar *)"outstanding-confirmed-commit"
Reserved YumaPro error-app-tag Values

Several values are defined by YumaPro conventions and are built into the server implementation. These cannot be redefined in the server.

#define RPC_ERR_APPTAG_GEN_WARNING      (const xmlChar *)"general-warning"
#define RPC_ERR_APPTAG_GEN_ERROR        (const xmlChar *)"general-error"
#define RPC_ERR_APPTAG_INT_ERROR        (const xmlChar *)"internal-error"
#define RPC_ERR_APPTAG_IO_ERROR         (const xmlChar *)"io-error"
#define RPC_ERR_APPTAG_MALLOC_ERROR     (const xmlChar *)"malloc-error"
#define RPC_ERR_APPTAG_LIMIT_REACHED    (const xmlChar *)"limit-reached"
#define RPC_ERR_APPTAG_LIBXML2_ERROR    (const xmlChar *)"libxml2-error"
#define RPC_ERR_APPTAG_SQL_ERROR        (const xmlChar *)"sql-error"
#define RPC_ERR_APPTAG_SSH_ERROR        (const xmlChar *)"ssh-error"
#define RPC_ERR_APPTAG_BEEP_ERROR       (const xmlChar *)"beep-error"
#define RPC_ERR_APPTAG_HTML_ERROR       (const xmlChar *)"html-error"
#define RPC_ERR_APPTAG_DATA_INCOMPLETE  (const xmlChar *)"data-incomplete"
#define RPC_ERR_APPTAG_DATA_INVALID     (const xmlChar *)"data-invalid"
#define RPC_ERR_APPTAG_DUPLICATE_ERROR  (const xmlChar *)"duplicate-error"
#define RPC_ERR_APPTAG_RESOURCE_IN_USE  (const xmlChar *)"resource-in-use"
#define RPC_ERR_APPTAG_NO_ACCESS        (const xmlChar *)"no-access"
#define RPC_ERR_APPTAG_RECOVER_FAILED   (const xmlChar *)"recover-failed"
#define RPC_ERR_APPTAG_NO_SUPPORT       (const xmlChar *)"no-support"
Reserved YANG Push error-app-tag Values

Several values are defined by Subscribed Notifications (RFC 8639) and YANG Push (RFC 8641). These are built into the server implementation and used if the "yang-push" bundle is loaded into the server. These cannot be redefined in the server.

/*** ietf-subscribed-notifications ***/

#define RPC_ERR_APPTAG_NOTIF_DSCP_UNAVAILABLE \
    (const xmlChar *)"ietf-subscribed-notifications:dscp-unavailable"

#define RPC_ERR_APPTAG_NOTIF_ENCODING_UNSUPPORTED \
    (const xmlChar *)"ietf-subscribed-notifications:encoding-unsupported"

#define RPC_ERR_APPTAG_NOTIF_FILTER_UNAVAILABLE \
    (const xmlChar *)"ietf-subscribed-notifications:filter-unavailable"

#define RPC_ERR_APPTAG_NOTIF_FILTER_UNSUPPORTED \
    (const xmlChar *)"ietf-subscribed-notifications:filter-unsupported"

#define RPC_ERR_APPTAG_NOTIF_INSUFFICIENT_RESOURCES \
    (const xmlChar *)"ietf-subscribed-notifications:insufficient-resources"

#define RPC_ERR_APPTAG_NOTIF_NO_SUCH_SUBSCRIPTION \
    (const xmlChar *)"ietf-subscribed-notifications:no-such-subscription"

#define RPC_ERR_APPTAG_NOTIF_REPLAY_UNSUPPORTED \
    (const xmlChar *)"ietf-subscribed-notifications:replay-unsupported"

#define RPC_ERR_APPTAG_NOTIF_STREAM_UNAVAILABLE \
    (const xmlChar *)"ietf-subscribed-notifications:stream-unavailable"

/*** ietf-yang-push errors ***/

#define RPC_ERR_APPTAG_PUSH_CANT_EXCLUDE \
    (const xmlChar *)"ietf-yang-push:cant-exclude"

#define RPC_ERR_APPTAG_PUSH_DATASTORE_NOT_SUBSCRIBABLE \
    (const xmlChar *)"ietf-yang-push:datastore-not-subscribable"

#define RPC_ERR_APPTAG_PUSH_NO_SUCH_RESYNC \
    (const xmlChar *)"ietf-yang-push:no-such-subscription-resync"

#define RPC_ERR_APPTAG_PUSH_ON_CHANGE_UNSUPPORTED \
    (const xmlChar *)"ietf-yang-push:on-change-unsupported"

#define RPC_ERR_APPTAG_PUSH_ON_CHANGE_SYNC_UNSUPPORTED \
    (const xmlChar *)"ietf-yang-push:on-change-sync-unsupported"

#define RPC_ERR_APPTAG_PUSH_PERIOD_UNSUPPORTED \
    (const xmlChar *)"ietf-yang-push:period-unsupported"

#define RPC_ERR_APPTAG_PUSH_UPDATE_TOO_BIG \
    (const xmlChar *)"ietf-yang-push:update-too-big"

#define RPC_ERR_APPTAG_PUSH_SYNC_TOO_BIG \
    (const xmlChar *)"ietf-yang-push:sync-too-big"

#define RPC_ERR_APPTAG_PUSH_UNCHANGING_SELECTION \
    (const xmlChar *)"ietf-yang-push:unchanging-selection"

error-path

Optional field containing an XPath absolute path expression identifying the node that caused the error. Will be present if the user input can be associated with the error.

This field is generated by the server depending on the parameters passed to the "record error" API functions.

error-message

Optional field containing a short error message. The server always generates an error-message field. A default error message for the Return Status will be returned if no custom message has been configured or provided by a SIL or SIL-SA callback function.

error-info

This optional field is a container that has child data nodes representing additional error information. Some NETCONF errors require that certain error-info child nodes be present, depending on the error-tag value

The following 'error-tag' values have specific error-info elements that are required:

  • bad-attribute

  • bad-element

  • bad-namespace

  • session-id

The server will generate the required data automatically for these errors.

Error Handling APIs

The server has several APIs for setting error information that will be returned to a client in an <rpc-error> message. Each API allows different amounts of context-specific information included in the error response.

../_images/error_data_components.png

Return Status

The 'status_t' enumeration returned by the SIL or SIL-SA code will be checked. If not error information has been recorded, then the server will construct an <rpc-error> based on the return status

SET_ERROR

The 'SET_ERROR' macro is for internal programming errors only. DO NOT USE!

agt_record_error

The 'agt_record_error' API allows the basic error fields to be set

agt_record_error_errinfo

The 'agt_record_error_errinfo' API allows all fields to be set, including over-riding the default error-message and error-app-tag fields.

agt_record_warning

The 'agt_record_warning' API allows a warning to be sent instead of an error

Return Status (status_t)

The 'status_t' is used to convey internal or protocol error conditions.

  • The internal status codes are defined in ncx/status_enum.h

  • The value 0 (NO_ERR) is used to indicate success.

  • Values 1 to 999 are error codes.

  • Values 1000 to 1999 are warning codes

  • Values 2000 to 2999 are system return codes

  • Error strings are defined in netconf/src/ncx/status.c in the 'get_error_string' function for each enumeration defined in status_enum.h

  • Each status_t enumeration is mapped to an 'error-tag' value in the file netconf/src/agt/agt_rpcerr.c in the function 'get_rpcerr'. This mapping also includes the default 'error-app-tag' value

  • The default 'error-message' and 'error-app-tag' values can be overridden using the YANG statements with the same name. These fields can also be specified internally (without adding these YANG statements) using the 'agt_record_error_errinfo' function instead of the 'agt_record_error' function.

const char *get_error_string(status_t res)

Get the error message for a specific internal error.

Parameters

res -- internal status_t error code

Returns

const pointer to error message

SET_ERROR

The 'SET_ERROR' macro is for programming errors, not client errors! The macro is defined in ncx/status.h and it is for flagging internal errors only. This macro must not be used for normal errors.

Warning

Do not use the SET_ERROR macro to return normal errors. This macro will cause “assert(0)” to be invoked, halting program execution.

This macro is used in some programming constructs to detect certain programming errors.

For example, switch statements for "enum" switches are required to have a "default" clause in YumaPro code. All known enum values must be handled. If a new enum is defined then the SET_ERROR will be hit if the switch has not been updated properly to handle the new value.

switch (cbtyp) {
case AGT_CB_VALIDATE:
    /* description-stmt validation here */
    break;
case AGT_CB_APPLY:
    /* database manipulation done here */
    break;
case AGT_CB_COMMIT:
    /* device instrumentation done here */
    switch (editop) {
    case OP_EDITOP_LOAD:
        break;
    case OP_EDITOP_MERGE:
        break;
    case OP_EDITOP_REPLACE:
        break;
    case OP_EDITOP_CREATE:
        break;
    case OP_EDITOP_DELETE:
        break;
    default:
        res = SET_ERROR(ERR_INTERNAL_VAL);
    }
    break;
case AGT_CB_ROLLBACK:
    /* undo device instrumentation here */
    break;
default:
    res = SET_ERROR(ERR_INTERNAL_VAL);
}

agt_record_error

NETCONF and RESTCONF use a common data structure to report errors, defined in RFC 6241#section-4.3.

Server errors are recorded with a set of APIs that allow complete control of the <rpc-error> contents.

The 'agt_record_error' function is one of the main error handling functions used in SIL or SIL-SA code.

If a specific error is not recorded by SIL code when an EDIT1 or EDIT2 callback is invoked. then the server will record a generic error, based on the 'status_t' returned by the SIL code.

void agt_record_error(ses_cb_t *scb, xml_msg_hdr_t *msghdr, ncx_layer_t layer, status_t res, const xml_node_t *xmlnode, ncx_node_t parmtyp, const void *error_info, ncx_node_t nodetyp, void *error_path)

Generate an rpc_err_rec_t and save it in the msg.

This is the main SIL or SIL-SA API to return an <rpc-error> to the NETCONF or RESTCONF client.

Parameters
  • scb --

    session control block

    NULL and no stats will be recorded

  • msghdr --

    XML msg header with error Q

    NULL, no errors will be recorded!

  • layer -- netconf layer error occured. Used for <error-type> field.

  • res -- internal error code. Will get mapped to <error-tag> and <error-app-tag>

  • xmlnode --

    XML node causing error. Will be used for <bad-element> and maybe error <error-path>

    NULL if not available

  • parmtyp -- type of node in 'error_info'

  • error_info --

    error data, specific to 'res'. Used to generate <error-info> sub-elements.

    NULL if not available (then nodetyp ignored)

  • nodetyp -- type of node in 'error_path'

  • error_path --

    internal data node with the error. Used to generate the <error-path> field

    NULL if not available or not used

Return values
  • msghdr->errQ -- has error message added if not NULL and no malloc errors

  • scb->stats -- may be updated if scb non-NULL

Example:

/* if error: set the res, errorstr, and errorval parms */
if (res != NO_ERR) {
    agt_record_error(scb,               // session control block
                     RPC_MHDR(msg),     // xml_msg_hdr for the request state
                     NCX_LAYER_CONTENT, // <error-type> enum
                     res,               // internal status code
                     NULL,              // XML node (parser mode)
                     NCX_NT_STRING,     // node type for error_info parm
                     errorstr,          // node value for <error-info> struct
                     NCX_NT_VAL,        // node type for the error_path parm
                     errorval);         // node for the <error-path> field
}

This API function generates an rpc_err_rec_t and save it in the message.

agt_record_error_errinfo

The agt_record_error_errinfo function can be used to report extended error information.

In order to add any extended error information to the response message, the agt_record_error_errinfo function should be used. This function is the same as the previous one if the errinfo parameter is set to NULL.

void agt_record_error_errinfo(ses_cb_t *scb, xml_msg_hdr_t *msghdr, ncx_layer_t layer, status_t res, const xml_node_t *xmlnode, ncx_node_t parmtyp, const void *error_info, ncx_node_t nodetyp, void *error_path, const ncx_errinfo_t *errinfo)

Generate an rpc_err_rec_t and save it in the msg Use provided error fields.

This is the main SIL or SIL-SA API to return an <rpc-error> to the NETCONF or RESTCONF client.

Parameters
  • scb --

    session control block

    NULL and no stats will be recorded

  • msghdr --

    XML msg header with error Q

    NULL, no errors will be recorded!

  • layer -- netconf layer error occured. Used for <error-type> field.

  • res -- internal error code. Will get mapped to <error-tag> and <error-app-tag>

  • xmlnode --

    XML node causing error. Will be used for <bad-element> and maybe error <error-path>

    NULL if not available

  • parmtyp -- type of node in 'error_info'

  • error_info --

    error data, specific to 'res'. Used to generate <error-info> sub-elements.

    NULL if not available (then nodetyp ignored)

  • nodetyp -- type of node in 'error_path'

  • error_path --

    internal data node with the error. Used to generate the <error-path> field

    NULL if not available or not used

  • errinfo -- error info record to use (may be NULL)

Return values
  • msghdr->errQ -- has error message added if not NULL and no malloc errors

  • scb->stats -- may be updated if scb non-NULL

agt_record_warning

The agt_record_warning function can be used to report a warning instead of an error.

An <rpc-error> element will still be used, however, the <error-severity> will be set to warning instead of error

void agt_record_warning(ses_cb_t *scb, xml_msg_hdr_t *msghdr, ncx_layer_t layer, status_t res, const xml_node_t *xmlnode, ncx_node_t parmtyp, const void *error_parm, ncx_node_t nodetyp, void *error_path, const ncx_errinfo_t *errinfo)

Generate an rpc_err_rec_t and save it in the msg.

Not actually supported in NETCONF protocol because no standard error-tag values are defined for severity=warning

Use the provided error info record for <rpc-error> fields Set the error-severity field to warning instead of error but only if agt_with_warnings is TRUE

Parameters
  • scb --

    session control block

    NULL and no stats will be recorded

  • msghdr --

    XML msg header with error Q

    NULL, no errors will be recorded!

  • layer -- netconf layer error occured. Used for <error-type> field.

  • res -- internal error code. Will get mapped to <error-tag> and <error-app-tag>

  • xmlnode --

    XML node causing error. Will be used for <bad-element> and maybe error <error-path>

    NULL if not available

  • parmtyp -- type of node in 'error_info'

  • error_parm --

    error data, specific to 'res'. Used to generate <error-info> sub-elements.

    NULL if not available (then nodetyp ignored)

  • nodetyp -- type of node in 'error_path'

  • error_path --

    internal data node with the error. Used to generate the <error-path> field

    NULL if not available or not used

  • errinfo -- error info record to use (may be NULL)

Return values
  • msghdr->errQ -- has error message added if not NULL and no malloc errors

  • scb->stats -- may be updated if scb non-NULL

SIL Creation of Custom Error Messages And AppInfo

The server maintains data structures for error information including the ncx_errinfo_t struct.

struct ncx_errinfo_t

YANG error info statement struct used to override default error handling in the server.

Public Members

dlq_hdr_t qhdr

queue header

xmlChar *descr

description-stmt (not saved on server)

xmlChar *ref

reference-stmt (not saved on server)

xmlChar *error_app_tag

error-app-tag field

xmlChar *error_message

error-message field

boolean seen

for yangdiff

Normally this structure is static and derived from YANG statements.

It can also be provided at runtime by a SIL function using the agt_record_error_errinfo function.

This feature must be used with care because the strings within the structure and the structure itself must persist until the server has sent the error response message.

Only the error_app_tag and error_message fields will be checked. Do not set any other fields in this structure.

ncx_errinfo_t *ncx_new_errinfo(void)

Malloc and init a new ncx_errinfo_t.

Returns

pointer to malloced ncx_errinfo_t, or NULL if memory error

Returns

pointer to malloced ncx_errinfo_t, or NULL if memory error

void ncx_free_errinfo(ncx_errinfo_t *err)

Scrub the memory in a ncx_errinfo_t by freeing all the sub-fields, then free the errinfo struct.

Parameters

err -- ncx_errinfo_t data structure to free

These strings are malloced (not const!) so the struct has to be setup by the SIL code.

The following example API shows how the ncx_errinfo_t struct can be created.

/* return an ncx_errinfo that must be freed with ncx_free_appinfo */
static ncx_errinfo_t *
    make_errinfo (const xmlChar *apptag,
                 const xmlChar *errmsg)
{
    ncx_errinfo_t *errinfo = ncx_new_errinfo();
    if (errinfo == nullptr) {
        return ERR_INTERNAL_MEM;
    }
    if (apptag != nullptr) {
        errinfo->error_app_tag = xml_strdup(apptag);
        if (errinfo->error_app_tag == nullptr) {
            ncx_free_errinfo(errinfo);
            return ERR_INTERNAL_MEM;
        }
    }
    if (errmsg != nullptr) {
        errinfo->error_message = xml_strdup(errmsg);
        if (errinfo->error_message == nullptr) {
            ncx_free_errinfo(errinfo);
            return ERR_INTERNAL_MEM;
        }
    }
    return errinfo;
}

SIL-SA Creation of Custom Error Messages And AppInfo

The following API provides a way to set a Custom Error message. In order to set a custom Error Message to be sent to the server the following API can be used. This API is only available to EDIT callbacks.

The following YANG module is used in this example:

module error-example {
  namespace "http://netconfcentral.org/ns/error-example";
  prefix "err";

  revision 2021-11-19 {
    description
      "Example module";
  }

  container test-silsa-errors {                      // EDIT2 CB
    presence "Presence";

    leaf test-error {
      type string;
    }
  }
}

Assume the SIL-SA code reports an error in case the 'test-error' leaf creation and when the value of a new leaf is force-error.

In this case there is an API 'sil_sa_set_error_msg' to set up a custom error to be returned to the server.

void sil_sa_set_error_msg(rpc_msg_t *msg, const xmlChar *strval)

Set Error Message string in case of error.

Parameters
  • msg -- rpc msg to use to find keys

  • strval -- string value to use for error message


status_t
    u_err_test_silsa_errors_edit (
    ses_cb_t *scb,
    rpc_msg_t *msg,
    agt_cbtyp_t cbtyp,
    op_editop_t editop,
    val_value_t *newval,
    val_value_t *curval)
{
    val_value_t *child_val = NULL;
    if (newval) {
        child_val =
            val_find_child(newval,
                           (const xmlChar *)"error-example",
                           (const xmlChar *)"test-error");
    }

    /* Get the rpc message id (txid_str) from the rpc_sil_sa_cb */
    const xmlChar *msg_id = sil_sa_get_rpc_msg_id(msg);

    /* Get the user_id value from the message header */
    const xmlChar *user = sil_sa_get_username();

    /* Get the client address (client_addr value from the message header) */
    const xmlChar *client_addr = sil_sa_get_client_addr();

    if (LOGDEBUG3) {
        log_debug3("\nEnter u_err_test_silsa_errors_edit");
        log_debug3_append("\nmsg_id:%s", msg_id);
        log_debug3_append("\nuser:%s", user);
        log_debug3_append("\nclient_addr:%s", client_addr);
    }

    switch (cbtyp) {
    case AGT_CB_VALIDATE:
        /* description-stmt validation here */
        break;
    case AGT_CB_APPLY:
        /* database manipulation done here */
        break;
    case AGT_CB_COMMIT:
        /* device instrumentation done here */
        switch (editop) {
        case OP_EDITOP_LOAD:
            break;
        case OP_EDITOP_MERGE:
            break;
        case OP_EDITOP_REPLACE:
            break;
        case OP_EDITOP_CREATE:

            if (child_val &&
                !xml_strcmp(VAL_STR(child_val), (const xmlChar *)"force-error")) {

                /* TRIGGER ERROR */
                res = ERR_NCX_OPERATION_NOT_SUPPORTED;

                sil_sa_set_error_msg(msg,
                                     (const xmlChar *)"SOME CUSTOM ERROR MSG");
            }

            break;
        case OP_EDITOP_DELETE:
            break;
        default:
            /* USE SET_ERROR FOR PROGRAMMING BUGS ONLY */
            res = SET_ERROR(ERR_INTERNAL_VAL);
        }
        break;
    case AGT_CB_ROLLBACK:
        /* undo device instrumentation here */
        break;
    default:
        /* USE SET_ERROR FOR PROGRAMMING BUGS ONLY */
        res = SET_ERROR(ERR_INTERNAL_VAL);
    }
    return res;

} /* u_err_test_silsa_errors_edit */

Adding <error-info> Data to an Error Response

The NETCONF <rpc-error> and RESTCONF <errors> data structures allow error data to be reported to the client. This data can be located in a container called “error-info”. Typically, the server adds an “error-number” leaf to this container, but there are several NETCONF errors that require specific error data to be returned.

Step 1) Define the error-info Data

The new API functions require the following H files, which need to be included into the SIL C file:

#include “agt_util.h”
#include “rpc.h”

The data returned to an <error-info> record should be defined in a real YANG module.

In the example below, an abstract leaf called 'testdata' is defined:

leaf testdata {
  ncx:abstract;
  type int32;
  description
    "Example of template for user <error-info> data";
}

Step 2) Create a Data Node and Add it to the Error Response.

The 'rpc_msg_add_error_data' function can be used to add custom <error-info> child nodes to an error response.

Only SIL Callbacks Can Use This Function
void rpc_msg_add_error_data(rpc_msg_t *msg, val_value_t *val)

Add error data to the response message.

Used by the server only

Add a val_value_t node that will be used in the error response sent to the client

Parameters
  • msg -- rpc_msg_t to add data into

  • val -- malloced error data node to add to error record error-info !!! This value node will be consumed and freed later !!!

The data created must be malloced so it can be freed later with the 'val_free_value' function.

The example from sil_error/src/sil-error.c shows an instance of testdata being created:

/********************************************************************
* FUNCTION add_user_error_data
*
* Add an example error-info data node to the error that is returned
* to the client
*
* INPUTS:
*    msg == RPC message to add user error data
* RETURNS:
*   status
********************************************************************/
static status_t
    add_user_error_data (rpc_msg_t *msg)
{
    /* get the abstract leaf template */
    obj_template_t *testdata_obj =
        obj_find_template_top(sil_error_mod,
                              sil_error_mod->name,
                              (const xmlChar *)"testdata");

    if (testdata_obj == NULL) {
        return ERR_NCX_DEF_NOT_FOUND;
    }

    /* a real add_user_data callback would probably get a system
     * value instead of a constant; use constant here
     */
    const xmlChar *valuebuff = (const xmlChar *)"42";

    status_t res = NO_ERR;
    val_value_t *testdata_val =
        val_make_simval_obj(testdata_obj, valuebuff, &res);

    if (testdata_val) {
        rpc_msg_add_error_data(msg, testdata_val);
    }

    return res;

}  /* add_user_error_data */

This step is done BEFORE the agt_record_error (or other variant) is called by the SIL code:

if (res != NO_ERR) {

     /* add user data to the message before calling agt_record_error */
     (void)add_user_error_data(msg);

     agt_record_error(
         scb,
         &msg->mhdr,
         NCX_LAYER_CONTENT,
         res,
         NULL,
         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
         errorval,
         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
         errorval);
 }

Example Error Responses

The “sil-trigger” object is used to show how error-info data is added to the error response.

The error is triggered by setting /sil-phase to apply and /sil-trigger to any value. This will force an error to generated upon the appropriate edit.

NETCONF Error Example

> merge /sil-trigger value=2

This might cause the following <rpc-error> response in NETCONF:

<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply message-id="4" trace-id="4"
 xmlns:silerr="http://www.netconfcentral.org/ns/sil-error"
 xmlns:ncx="http://netconfcentral.org/ns/yuma-ncx"
 xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
 <rpc-error>
  <error-type>application</error-type>
  <error-tag>operation-failed</error-tag>
  <error-severity>error</error-severity>
  <error-app-tag>general-error</error-app-tag>
  <error-path>/silerr:sil-trigger</error-path>
  <error-message xml:lang="en">operation failed</error-message>
  <error-info>
   <error-number>274</error-number>
   <testdata xmlns="http://www.netconfcentral.org/ns/sil-error">42</testdata>
  </error-info>
 </rpc-error>
</rpc-reply>

RESTCONF Error Example

The following example shows how RESTCONF will return the user error data:

If any errors occur while attempting to invoke the operation, then an "errors" data structure is returned with the appropriate error status.

XML Example

The client might send the following POST request message to invoke a "reboot" operation:

POST /restconf/operations/example-ops:reboot HTTP/1.1
Accept: application/yang-data+xml
Content-Type: application/yang-data+xml

<input xmlns="https://example.com/ns/example-ops">
  <delay>-33</delay>
  <message>Going down for system maintenance</message>
  <language>en-US</language>
</input>

The server might respond with an "invalid-value" error in default XML format:

HTTP/1.1 400 Bad Request
Date: Mon, 25 Apr 2012 11:10:30 GMT
Server: example-server
Content-Type: application/yang-data+xml

<errors xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf">
  <error>
    <error-type>protocol</error-type>
    <error-tag>invalid-value</error-tag>
    <error-path xmlns:err="https://example.com/ns/example-ops">err:input/err:delay</error-path>
    <error-message>Invalid input parameter</error-message>
  </error>
</errors>

JSON Example

If the client specifies application/yang-data+json in the Accept header entry, the server will respond in JSON format.

A lock-denied error might be sent as follows:

HTTP/1.1 409 Conflict
Date: Mon, 23 Apr 2012 17:11:00 GMT
Server: example-server
Content-Type: application/yang-data+json

{
   "ietf-restconf:errors": {
     "error": [
       {
         "error-type": "protocol",
         "error-tag": "lock-denied",
         "error-message": "Lock failed, lock already held"
       }
     ]
   }
 }

RPC Operation Interface

All RPC operations are data-driven within the server, using the YANG rpc statement for the operation and SIL callback functions.

Any new protocol operation can be added by defining a new YANG rpc statement in a module, and providing the proper SIL code.

RPC Callbacks

RPC Callback Template

The 'agt_rpc_method_t' typedef in agt/agt_rpc.h is used as the callback template for all RPC callback phases.

typedef status_t (*agt_rpc_method_t)(ses_cb_t *scb, rpc_msg_t *msg, xml_node_t *methnode)

Template for RPC server callbacks.

The same template is used for all RPC callback phases The callback is expected to validate if needed and then invoke if needed.

Param scb

session invoking the RPC

Param msg

message in progress for this <rpc> request

the msg->rpc_input value node contains the input (if any). It is a container matching the rpc/input node for the YANG rpc

Param methnode

XML node for the operation, which can be used in error reporting (or ignored)

Return

return status for the phase

  • An error in validate phase will cancel invoke phase

  • An rpc-error will be added if an error is returned and the msg error Q is empty

RPC Callback Initialization

The 'agt_rpc_register_method' function in agt/agt_rpc.h is used to provide a callback function for a specific callback phase. The same function can be used for multiple phases if desired.

This function call is usually generated automatically in the SIL or SIL-SA code stubs.

status_t agt_rpc_register_method(const xmlChar *module, const xmlChar *method_name, agt_rpc_phase_t phase, agt_rpc_method_t method)

add callback for 1 phase of RPC processing

Parameters
  • module -- name of the module that contains the RPC statement

  • method_name -- Identifier for the rpc statement

  • phase -- RPC server callback phase for this callback

    • AGT_PH_VALIDATE(0): validate phase

    • AGT_PH_INVOKE(1): invoke phase

    • AGT_PH_POST_REPLY(2): post-reply phase

  • method -- pointer to callback function to register

Returns

status of the operation

Example Registration Code:

res = agt_rpc_register_method(
    y_toaster_M_toaster,
    y_toaster_N_make_toast,
    AGT_RPC_PH_VALIDATE,
    y_toaster_make_toast_validate);
if (res != NO_ERR) {
    return res;
}

RPC Callback Cleanup

The 'agt_rpc_unregister_method' function in agt/agt_rpc.h is used to remove a callback function for all callback phases.

Warning

This function must be called once if any callback phase was registered. Memory may not be properly freed if an RPC callback is not properly unregistered.

This function call is usually generated automatically in the SIL or SIL-SA code stubs.

void agt_rpc_unregister_method(const xmlChar *module, const xmlChar *method_name)

remove the callback functions for all phases of RPC or Action processing for the specified RPC method or action

Parameters
  • module -- module name of RPC method or action name

  • method_name -- RPC method or action name

Example Unregistration Code:

agt_rpc_unregister_method(
    y_toaster_M_toaster,
    y_toaster_N_make_toast);

Force an RPC Operation to be Unsupported

It is possible for the SIL instrumentation to turn support for an RPC operation off in the server.

If this API is used then the RPC operation will be removed from the the list of operations that can be used in the server.

void agt_rpc_unsupport_method(const xmlChar *module, const xmlChar *method_name)

mark an RPC method or action as unsupported within the server

this is needed for operations dependent on capabilities

Parameters
  • module -- module name of RPC method (really module name)

  • method_name -- RPC method name

Use this function with care. It may break the server if this API is used on a server-provided RPC operation.

Example Usage:

agt_rpc_unsupport_method(
    y_toaster_M_toaster,
    y_toaster_N_make_toast);

RPC Message Header

The NETCONF server will parse the incoming XML message and construct an RPC message header, which is used to maintain state and any other message-specific data during the processing of an incoming <rpc> request.

rpc_msg_t

The 'rpc_msg_t' data structure defined in ncx/rpc.h is used to represent an RPC operation. It is a high-level control block which contains a low-level 'xml_msg_hdr_t' struct within it.

struct rpc_msg_t

NETCONF Server and Client RPC Request/Reply Message Header.

Public Members

dlq_hdr_t qhdr

Queue header to store RPC messages in a queue (within the session header)

xml_msg_hdr_t mhdr

generic XML message header Most in-message state is kept in the mhdr There are several places in the code where the mhdr is used alone so there is no coupling to the RPC layer.

Contains XML message prefix map and other data used to parse the request and generate the reply.

xml_attrs_t *rpc_in_attrs

incoming: top-level rpc element data Queue of xml_attr_t representing any XML attributes that were present in the <rpc> element.

A callback function may add xml_attr_t structs to this queue to send in the reply.

struct obj_template_t_ *rpc_method

incoming: Back-pointer to the object template for this RPC operation.

2nd-level method name element data, used in agt_output_filter to check get or get-config; cannot import obj.h here! The object template type will be OBJ_TYP_RPC for an RPC method For an action this will be the top-level <action> RPC wrapper in the YANG namespace

int rpc_agt_state

incoming: SERVER RPC processing state Enum value (0, 1, 2) for the current RPC callback phase.

op_errop_t rpc_err_option

Enum value for the <error-option> parameter.

This is only set if an edit operation is in progress.

op_editop_t rpc_top_editop

Enum value for the <default-operation> parameter.

This is only set if an edit operation is in progress.

val_value_t *rpc_input

Value tree representing the container of 'input' parameters for this RPC operation.

For an action, the rpc_input node is not used. Instead the rpc_actionval backptr is used instead. This is malloced in rpc_new_msg but not filled in.

struct sil_sa_cb_t_ *rpc_sil_sa_cb

backptr to SIL-SA edit control block if WITH_YCONTROL=1

dlq_hdr_t rpc_inputQ

the rpc_inputQ is used with JSON encoded input since an array is allowed at the top-level; it is used instead of rpc_input if encoding == JSON, even if only 1 array node is parsed;

Q of val_value_t *

void *rpc_user1

Void pointer that can be used by method routines to save context or whatever to store SIL-specific data.

This pointer is preserved, transferred from validate to invoke so data does not need to be regenerated or retrieved

void *rpc_user2

Same use as rpc_user1.

uint32 rpc_returncode

Internal return code used to control nested callbacks.

rpc_data_t rpc_data_type

incoming: get method reply handling builtin For RPC operations that return data, this enumeration MUST be set to indicate which type of data is returned.

  • RPC_DATA_STD: A <data> container will be used to encapsulate any returned data, within the <rpc-reply> element.

  • RPC_DATA_YANG: The <rpc-reply> element will be the only container encapsulated any returned data. If the rpc_datacb is non-NULL then it will be used as a callback to generate the rpc-reply inline, instead of buffering the output. The rpc_data and rpc_filter parameters are optionally used by the rpc_datacb function to generate a reply.

void *rpc_datacb

For operations that return streamed data, this pointer is set to the desired callback function to use for generated the data portion of the <rpc-reply> XML response.

The template for this callback is agt_rpc_data_cb_t, found in agt_rpc.h

dlq_hdr_t rpc_dataQ

For operations that return stored data, this queue of val_value_t structures can be used to provide the response data.

Each val_value_t structure will be encoded as one of the corresponding RPC output parameters. The data will be returned in order. The val_value_t nodes will be freed when the rpc_msg_t is freed

op_filter_t rpc_filter

Internal structure for optimizing subtree and XPath retrieval operations.

backptrs for get* methods.

struct agt_cfg_transaction_t_ *rpc_txcb

incoming: agent database edit transaction control block must be freed by an upper layer if set to malloced data

boolean rpc_parse_errors

load-config parse-error and startup-error=continue flag if the val_purge_errors_from_root function is needed

xmlChar *rpc_message_id

debugging and audit message string.

Contains the message-id attribute found in the <rpc> header. backptr into rpc_in_attrs.

xmlChar *rpc_trace_id

debugging and audit message string.

Contains the trace-id attribute found in the <rpc> header. backptr into rpc_in_attrs.

boolean rpc_replay_config

TRUE if this RPC is being called in replay config mode.

boolean rpc_with_template

with-template parameter was seen in the validate phase

dlq_hdr_t hook_inputQ

points to add_edit_value node comming from the users freed in the end of transaction.

Used only with set-hook. Contains Q of val_value_t.

boolean rpc_defer_reply

YPSERVER mode is skipping the regular rpc-reply phase and will send the reply after doing the remote task.

xml_attrs_t rpc_defer_in_attrs

the top->attrs gets deleted so a deferred rpc-reply needs to save the rpc_in_attrs.

This is malloced and cleaned when rpc-msg_t is freed

rpc_rpytyp_t rpc_reply_type

saved reply type needed for audit record

status_t rpc_status

saved processing status for audit record

time_t rpc_start_time

saved timestamp when started for audit record

const xmlChar *subrpc_filespec

saved by agt_db_api.c so the ycontrol callback function can generate an external value with the DB-API subrpc response message

RPC Validate Callback Function

../_images/rpc_validate_phase.png

The RPC validate callback function is optional to use. Its purpose is to validate any aspects of an RPC operation, beyond the constraints checked by the server engine. Only 1 validate function can register for each YANG rpc statement. The standard NETCONF operations are reserved by the server engine. There is usually zero or one of these callback functions for every 'rpc' statement in the YANG module associated with the SIL code.

It is enabled with the agt_rpc_register_method function, within the phase 1 initialization callback function.

The yangdump-sdk code generator will create this SIL callback function by default. There will be C comments in the code to indicate where your additional C code should be added.

The val_find_child (or val_child_find) function is commonly used to find particular parameters within the RPC input section, which is encoded as a val_value_t tree.

The agt_record_error function is commonly used to record any parameter or other errors. In the libtoaster example, there are internal state variables ("toaster_enabled" and "toaster_toasting"), maintained by the SIL code, which are checked in addition to any provided parameters.

Example RPC YANG definition:

    rpc make-toast {
        description
          "Make some toast.
           The toastDone notification will be sent when
           the toast is finished.
           An 'in-use' error will be returned if toast
           is already being made.
           A 'resource-denied' error will be returned
           if the toaster service is disabled.";
        input {
            leaf toasterDoneness {
                type uint32 {
                    range "1 .. 10";
                }
                default 5;
                description
                  "This variable controls how well-done is the
                   ensuing toast. It should be on a scale of 1 to 10.
                   Toast made at 10 generally is considered unfit
                   for human consumption; toast made at 1 is warmed
                   lightly.";
            }
            leaf toasterToastType {
                type identityref {
                    base toast:toast-type;
                }
                default toast:wheat-bread;
                description
                  "This variable informs the toaster of the type of
                   material that is being toasted. The toaster
                   uses this information, combined with
                   toasterDoneness, to compute for how
                   long the material must be toasted to achieve
                   the required doneness.";
            }
        }
    }

Example SIL Function:


/********************************************************************
* FUNCTION y_toaster_make_toast_validate
*
* RPC validation phase
* All YANG constraints have passed at this point.
* Add description-stmt checks in this function.
*
* INPUTS:
*     see agt/agt_rpc.h for details
*
* RETURNS:
*     error status
********************************************************************/
static status_t
    y_toaster_make_toast_validate (
        ses_cb_t *scb,
        rpc_msg_t *msg,
        xml_node_t *methnode)
{
    status_t res;
    val_value_t *errorval;
    const xmlChar *errorstr;
    val_value_t *toasterDoneness_val;
    val_value_t *toasterToastType_val;
    uint32 toasterDoneness;
    val_idref_t *toasterToastType;

    res = NO_ERR;
    errorval = NULL;
    errorstr = NULL;

    toasterDoneness_val = val_find_child(
        msg->rpc_input,
        y_toaster_M_toaster,
        y_toaster_N_toasterDoneness);
    if (toasterDoneness_val != NULL && toasterDoneness_val->res == NO_ERR) {
        toasterDoneness = VAL_UINT(toasterDoneness_val);
    }

    toasterToastType_val = val_find_child(
        msg->rpc_input,
        y_toaster_M_toaster,
        y_toaster_N_toasterToastType);
    if (toasterToastType_val != NULL && toasterToastType_val->res == NO_ERR) {
        toasterToastType = VAL_IDREF(toasterToastType_val);
    }



    /* added code starts here */
    if (toaster_enabled) {
        /* toaster service enabled, check if in use */


        if (toaster_toasting) {
            res = ERR_NCX_IN_USE;
        } else {
            /* this is where a check on bread inventory would go */

            /* this is where a check on toaster HW ready would go */
        }
    } else {
        /* toaster service disabled */
        res = ERR_NCX_RESOURCE_DENIED;
    }
    /* added code ends here */

    /* if error: set the res, errorstr, and errorval parms */
    if (res != NO_ERR) {
        agt_record_error(
            scb,
            &msg->mhdr,
            NCX_LAYER_OPERATION,
            res,
            methnode,
            NCX_NT_STRING,
            errorstr,
            NCX_NT_VAL,
            errorval);
    }

    return res;

} /* y_toaster_make_toast_validate */

RPC Invoke Callback Function

../_images/rpc_invoke_phase.png

The RPC invoke callback function is used to perform the operation requested by the client session. Only 1 invoke function can register for each YANG rpc statement. The standard NETCONF operations are reserved by the server engine. There is usually one of these callback functions for every 'rpc' statement in the YANG module associated with the SIL code.

The RPC invoke callback function is optional to use, although if no invoke callback is provided, then the operation will have no affect. Normally, this is only the case if the module is be tested by an application developer, using netconfd-pro as a server simulator.

It is enabled with the agt_rpc_register_method function, within the phase 1 initialization callback function.

The yangdump-sdk code generator will create this SIL callback function by default. There will be C comments in the code to indicate where your additional C code should be added.

For RPC operations that return either an <ok> or <rpc-error> response, there is nothing more required of the RPC invoke callback function.

For operations which return some data or <rpc-error>, the SIL code must do 1 of 2 additional tasks:

  • add a val_value_t structure to the 'rpc_dataQ' queue in the rpc_msg_t for each parameter listed in the YANG rpc 'output' section.

  • set the 'rpc_datacb' pointer in the rpc_msg_t structure to the address of your data reply callback function.

Example RPC Invoke SIL Function Registration

res = agt_rpc_register_method(
    y_toaster_M_toaster,
    y_toaster_N_make_toast,
    AGT_RPC_PH_INVOKE,
    y_toaster_make_toast_invoke);
if (res != NO_ERR) {
    return res;
}

Example RPC Invoke SIL Function


/********************************************************************
* FUNCTION y_toaster_make_toast_invoke
*
* RPC invocation phase
* All constraints have passed at this point.
* Call device instrumentation code in this function.
*
* INPUTS:
*     see agt/agt_rpc.h for details
*
* RETURNS:
*     error status
********************************************************************/
static status_t
    y_toaster_make_toast_invoke (
        ses_cb_t *scb,
        rpc_msg_t *msg,
        xml_node_t *methnode)
{
    status_t res;
    val_value_t *toasterDoneness_val;
    val_value_t *toasterToastType_val;
    uint32 toasterDoneness;
    val_idref_t *toasterToastType;

    res = NO_ERR;
    toasterDoneness = 0;

    toasterDoneness_val = val_find_child(
        msg->rpc_input,
        y_toaster_M_toaster,
        y_toaster_N_toasterDoneness);
    if (toasterDoneness_val != NULL && toasterDoneness_val->res == NO_ERR) {
        toasterDoneness = VAL_UINT(toasterDoneness_val);
    }

    toasterToastType_val = val_find_child(
        msg->rpc_input,
        y_toaster_M_toaster,
        y_toaster_N_toasterToastType);
    if (toasterToastType_val != NULL && toasterToastType_val->res == NO_ERR) {
        toasterToastType = VAL_IDREF(toasterToastType_val);
    }

    /* invoke your device instrumentation code here */

    /* make sure the toasterDoneness value is set */
    if (toasterDoneness_val == NULL) {
        toasterDoneness = 5;   /* set the default */
    }


    /* arbitrary formula to convert toaster doneness to the
     * number of seconds the toaster should be on
     */
    toaster_duration = toasterDoneness * 12;

    /* this is where the code would go to adjust the duration
     * based on the bread type
     */

    if (LOGDEBUG) {
        log_debug("\ntoaster: starting toaster for %u seconds",
                  toaster_duration);
    }

    /* this is where the code would go to start the toaster
     * heater element
     */

    /* start a timer to toast for the specified time interval */
    res = agt_timer_create(toaster_duration,
                           FALSE,
                           toaster_timer_fn,
                           NULL,
                           &toaster_timer_id);
    if (res == NO_ERR) {
        toaster_toasting = TRUE;
    } else {
        agt_record_error(
            scb,
            &msg->mhdr,
            NCX_LAYER_OPERATION,
            res,
            methnode,
            NCX_NT_NONE,
            NULL,
            NCX_NT_NONE,
            NULL);
    }
    /* added code ends here */

    return res;

} /* y_toaster_make_toast_invoke */

RPC Data Output Handling

RPC operations can return data to the client if the operation succeeds. The YANG “output” statement defines the return data for each RPC operation. Constructing YANG data is covered in detail elsewhere. This section shows a simple example SIL invoke function that returns data.

The output data is usually constructed with APIs like agt_make_leaf from agt/agt_util.h.

To use these APIs, the output object is needed.

The following code can be used to get this object:

obj_template_t *obj = RPC_MSG_METHOD(msg);
if (obj) {
    obj = obj_find_child(obj, NULL, NCX_EL_OUTPUT);
}
if (obj == NULL) {
    return ERR_NCX_DEF_NOT_FOUND;  // should not happen
}

Once this object template is retrieved from the 'msg' parameter data can be added to the 'msg' using val_value_t structures.

The following snippet shows a leaf being created using an int32 value:

int32 result = 42;
val_value_t *val =
     agt_make_int_leaf(obj,
                       y_addrpc_N_sum,
                       result,
                       &res);

After the value is created it must be added to the message using the 'agt_rpc_add_return_val' API:

void agt_rpc_add_return_val(val_value_t *return_val, rpc_msg_t *msg)

Add a return value to the msg.

Parameters
  • return_val -- value to add

  • msg -- message to add value into

if (val) {
   agt_rpc_add_return_val(val, msg);
}

RPC Data Output Example

Example YANG Module:

module addrpc {
  namespace "http://www.yumaworks.com/ns/addrpc";
  prefix add;
  revision "2020-02-25";

  rpc add {
    description "Get the sum of two numbers";
    input {
      leaf num1 {
        type int32;
        mandatory true;
        description "First number to add";
      }
      leaf num2 {
        type int32;
        mandatory true;
        description "Second number to add";
      }
    }
    output {
      leaf sum {
        type int32;
        mandatory true;
        description "The sum of the 2 numbers";
      }
    }
   }
}

Example SIL Invoke Function Returning Output Data:

/********************************************************************
* FUNCTION y_addrpc_add_invoke
*
* RPC invocation phase
* All constraints have passed at this point.
* Call device instrumentation code in this function.
*
* INPUTS:
*     see agt/agt_rpc.h for details
*
* RETURNS:
*     error status
********************************************************************/
static status_t y_addrpc_add_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 <add> from module addrpc");
    }

    val_value_t *inputval = agt_get_rpc_input(msg);
    if (inputval == NULL) return ERR_NCX_OPERATION_FAILED;

    val_value_t *v_num1_val = NULL;
    int32 v_num1 = 0;

    val_value_t *v_num2_val = NULL;
    int32 v_num2 = 0;

    v_num1_val = val_find_child(
        inputval,
        y_addrpc_M_addrpc,
        y_addrpc_N_num1);
    if (v_num1_val) {
        v_num1 = VAL_INT(v_num1_val);
    }

    v_num2_val = val_find_child(
        inputval,
        y_addrpc_M_addrpc,
        y_addrpc_N_num2);
    if (v_num2_val) {
        v_num2 = VAL_INT(v_num2_val);
    }

    /* remove the next line if scb is used */
    (void)scb;

    /* remove the next line if methnode is used */
    (void)methnode;

    /* invoke your device instrumentation code here */

    /* Following output nodes expected:
     * leaf sum
     */
    int32 result = v_num1 + v_num2;
    obj_template_t *obj = RPC_MSG_METHOD(msg);
    if (obj) {
        obj = obj_find_child(obj, NULL, NCX_EL_OUTPUT);
    }
    if (obj == NULL) {
        return ERR_NCX_DEF_NOT_FOUND;  // should not happen
    }

    val_value_t *val =
        agt_make_int_leaf(obj,
                          y_addrpc_N_sum,
                          result,
                          &res);
    if (val) {
        agt_rpc_add_return_val(val, msg);
    }

    return res;

} /* y_addrpc_add_invoke */

RPC Post Reply Callback Function

../_images/rpc_post_reply_phase.png

The RPC post-reply callback function is used to clean up after a message has been processed. Only 1 function can register for each YANG rpc statement. The standard NETCONF operations are reserved by the server engine. This callback is not needed unless the SIL validate or invoke callback allocated some memory that needs to deleted after the <rpc-reply> is sent.

The RPC post reply callback function is optional to use. It is enabled with the 'agt_rpc_register_method' function, within the phase 1 initialization callback function.

The yangdump-pro code generator will not create this SIL callback function by default.

Example SIL Function Registration

Example SIL Function:

/********************************************************************
* FUNCTION y_foo_command_post
*
* RPC post reply phase
*
* INPUTS:
*     see agt/agt_rpc.h for details
*
* RETURNS:
*     error status
********************************************************************/
static status_t
    y_foo_command_post (
        ses_cb_t *scb,
        rpc_msg_t *msg,
        xml_node_t *methnode)
{
    (void)scb;
    (void)methnode;
    if (msg->rpc_user1 != NULL) {
        m__free(msg->rpc_user1);
        msg->rpc_user1 = NULL;
    }
    return NO_ERR;
}  /* y_foo_command_post */

YANG 1.1 Action Interface

A YANG action is like an RPC operation in many ways except it is associated with the data node where the action is defined. The SIL code for an action callback is a combination of the RPC callbacks and the EDIT callbacks. The call flow is the same as for RPC operations. The parameters are similar as well, except the ancestor keys for the instance are provided, so the SIL callback knows which instance to apply the action.

All YANG actions are data-driven within the server, using the YANG action statement for the operation and SIL callback functions.

Any new data-specific actions can be added by defining a new YANG action statement within a container or list, and providing the proper SIL code.

> make_sil_dir_pro ex-action

Example YANG Action

module ex-action {
  yang-version 1.1;
  namespace "http://netconfcentral.org/ns/ex-action";
  prefix exa;
  import ietf-yang-types { prefix yang; }
  revision 2020-03-06;

  list server {
    key name;
    leaf name {
      type string;
      description "Server name";
    }
    action reset {
      input {
        leaf reset-msg {
          type string;
          description "Log message to print before server reset";
        }
      }
      output {
        leaf reset-finished-at {
          type yang:date-and-time;
          description "Time the reset was done on the server";
        }
      }
    }
  }
}

Example YANG Action Request:

<?xml version="1.0" encoding="UTF-8"?>
<rpc message-id="1"
 xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
 <action xmlns="urn:ietf:params:xml:ns:yang:1">
   <server xmlns="http://netconfcentral.org/ns/ex-action">
     <name>test1</name>
     <reset>
       <reset-msg>diagnostic mode 1</reset-msg>
     </reset>
   </server>
 </action>
</rpc>

Example YANG Action Reply:

<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply message-id="1" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <reset-finished-at xmlns="http://netconfcentral.org/ns/ex-action">2020-03-08T19:01:22Z</reset-finished-at>
</rpc-reply>

Action Callbacks

Action Callback Template

The 'agt_action_cb_t' typedef is used for SIL and SIL-SA action callback functions.

typedef status_t (*agt_action_cb_t)(ses_cb_t *scb, rpc_msg_t *msg, xml_node_t *methnode, val_value_t *actionval)

Template for Action server callbacks.

The same template is used for all Action callback phases The callback is expected to validate if needed and then invoke if needed.

The entire input hierarchy for an action is contained in the msg->rpc_input node. The 'actionval' node passed to the callback points at the action node nested in the input (like <ping> in the example below)

      <action xmlns="urn:ietf:params:xml:ns:yang:1">
        <interfaces xmlns="http://netconfcentral.org/ns/t200">
          <interface>
            <name>eth1</name>
            <ping>
              <destination>192.0.2.1</destination>
            </ping>
          </interface>
        </interfaces>
      </action>

Param scb

The session control block for the session handling the action request.

Param msg

message in progress for this <rpc> request, containing the input parameters (if any).

Param methnode

The XML-specific node being parsed if available. This can used for error reporting if no 'val' or 'obj' parameters are available (from the request message).

Param actionval

The input node represents the start of the specific action being invoked. The auto-generated SIL code can derive all the ancestor keys from this data structure.

Return

status return status for the phase; an error in validate phase will cancel invoke phase; an rpc-error will be added if an error is returned and the msg error Q is empty

Action Callback Initialization

The 'agt_action_register_action' function in agt/agt_action.h is used to provide a callback function for a specific callback phase.

  • It is expected within the phase 1 initialization callback function.

  • The same function can be used for multiple phases if desired.

  • Only 1 validate ot action invoke function can register for each YANG action statement.

  • The yangdump-pro code generator will create this SIL callback function by default. There will C comments in the code to indicate where your additional C code should be added.

  • The registration for the action will also be generated automatically by yangdump-pro.

  • Separate callbacks and initialization/cleanup call are automatically generated by yangdump-pro for each action statement.

status_t agt_action_register_action(const xmlChar *defpath, agt_rpc_phase_t phase, agt_action_cb_t action_cb)

add callback for 1 phase of action processing

Parameters
  • defpath -- Xpath with default (or no) prefixes defining the object that will get the callbacks

  • phase -- action server callback phase for this callback

    • AGT_PH_VALIDATE(0): validate phase

    • AGT_PH_INVOKE(1): invoke phase

    • AGT_PH_POST_REPLY(2): post-reply phase

  • action_cb -- pointer to callback function to register

Returns

status of the operation

Example SIL Function Registration (found in y_ex-action.c)

res = agt_action_register_action(
    (const xmlChar *)"/exa:server/exa:reset",
    AGT_RPC_PH_VALIDATE,
    ex_action_server_reset_action_val);
if (res != NO_ERR) {
    return res;
}

res = agt_action_register_action(
    (const xmlChar *)"/exa:server/exa:reset",
    AGT_RPC_PH_INVOKE,
    ex_action_server_reset_action_inv);
if (res != NO_ERR) {
    return res;
}

Action Callback Cleanup

The 'agt_action_unregister_action' function in agt/agt_action.h is used to cleanup a callback function for a specific callback phase. The same function can be used for multiple phases if desired.

void agt_action_unregister_action(const xmlChar *defpath)

remove a callback for all phases of action processing

Parameters

defpath -- Xpath with default (or no) prefixes defining the object that will have the callbacks removed

Action Message Header

The NETCONF server will parse the incoming XML message and construct an RPC message header, which is used to maintain state and any other message-specific data during the processing of an incoming <rpc> request.

The rpc_msg_t data structure in ncx/rpc.h is used for this purpose. This is exactly the same as for an RPC operation.

Action Validate Callback Function

The action validate callback function is optional to use. Its purpose is to validate any aspects of an action request, beyond the constraints checked by the server engine.

  • The validate callback is optional to use.

  • Validation could be done in the validate or invoke phase with the same result.

Example User SIL Function:

(Note that the input parameters include the list key 'k_server_name' for the YANG list /server)

/********************************************************************
* FUNCTION u_ex_action_server_reset_action_val
*
* YANG 1.1 action validate callback
* Path: /server/reset
*
* INPUTS:
*     see agt/agt_action.h for details
*     k_ parameters are ancestor list key values.
*
* RETURNS:
*     error status
********************************************************************/
status_t u_ex_action_server_reset_action_val (
    ses_cb_t *scb,
    rpc_msg_t *msg,
    xml_node_t *methnode,
    val_value_t *actionval,
    const xmlChar *k_server_name)
{
    status_t res = NO_ERR;
    val_value_t *errorval = NULL;

    if (LOGDEBUG) {
        log_debug("\nEnter u_ex_action_server_reset_action_val for action <reset>");
    }

    val_value_t *v_reset_msg_val = NULL;
    const xmlChar *v_reset_msg;

    if (actionval) {
        v_reset_msg_val = val_find_child(
            actionval,
            y_ex_action_M_ex_action,
            y_ex_action_N_reset_msg);
        if (v_reset_msg_val) {
            v_reset_msg = VAL_STRING(v_reset_msg_val);
        }
    }

    /* the validate function would check here if it is OK to
     * reset the server right now; if not an error would be
     * returned by setting res to the correct error status.
     * If the input parameter is the problem then set errorval
     * otherwise leave errorval NULL
     */

    if (res != NO_ERR) {
        agt_record_error(
            scb,
            &msg->mhdr,
            NCX_LAYER_OPERATION,
            res,
            methnode,
            (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
            errorval,
            (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
            errorval);
    }
    return res;

} /* u_ex_action_server_reset_action_val */

Action Invoke Callback Function

The action invoke callback function is used to perform the data-specific action requested by the client session.

  • The action invoke callback function is optional to use, although if no invoke callback is provided, then the action will have no affect.

Example SIL Action Invoke Function:


/********************************************************************
* FUNCTION u_ex_action_server_reset_action_inv
*
* YANG 1.1 action invoke callback
* Path: /server/reset
*
* INPUTS:
*     see agt/agt_action.h for details
*     k_ parameters are ancestor list key values.
*
* RETURNS:
*     error status
********************************************************************/
status_t u_ex_action_server_reset_action_inv (
    ses_cb_t *scb,
    rpc_msg_t *msg,
    xml_node_t *methnode,
    val_value_t *actionval,
    const xmlChar *k_server_name)
{
    status_t res = NO_ERR;

    if (LOGDEBUG) {
        log_debug("\nEnter u_ex_action_server_reset_action_inv for action <reset>");
    }

    val_value_t *v_reset_msg_val = NULL;
    const xmlChar *v_reset_msg = NULL;

    if (actionval) {
        v_reset_msg_val = val_find_child(
            actionval,
            y_ex_action_M_ex_action,
            y_ex_action_N_reset_msg);
        if (v_reset_msg_val) {
            v_reset_msg = VAL_STRING(v_reset_msg_val);
        }
    }

    /* display the reset logging message */
    log_info("\nex-action: Resetting server %s\n", k_server_name);
    if (v_reset_msg) {
        log_info_append("%s\n", v_reset_msg);
    }

    /* the invoke function would schedule or execute the server reset here */

    /* remove the next line if scb is used */
    (void)scb;

    /* remove the next line if methnode is used */
    (void)methnode;

    /* invoke your device instrumentation code here */

    /* Following output nodes expected:
     * leaf reset-finished-at
     */
    if (actionval == NULL) {
        return ERR_NCX_OPERATION_FAILED;  // should not happen
    }

    obj_template_t *obj =
        obj_find_child(VAL_OBJ(actionval),
                       NULL,
                       NCX_EL_OUTPUT);
    if (obj == NULL) {
        return ERR_NCX_DEF_NOT_FOUND;  // should not happen
    }

    /* return the current time */
    xmlChar buff[TSTAMP_MIN_SIZE+1];
    tstamp_datetime(buff);

    val_value_t *val =
        agt_make_leaf(obj,
                      y_ex_action_N_reset_finished_at,
                      buff,
                      &res);
    if (retval) {
        agt_rpc_add_return_val(val, msg);
    } // else res set to error

    return res;

} /* u_ex_action_server_reset_action_inv */

Action Data Output Handling

YANG actions can return data to the client if the operation succeeds. The YANG “output” statement defines the return data for each YANG action. Constructing YANG data is covered in detail elsewhere. This section shows a simple example SIL action invoke function that returns data.

This procedure is the same for YANG Actions and RPC Operations. Refer to the RPC Data Output Handling section and follow these procedures.

Database Operations

The server database is designed so that the SIL callback functions do not need to really know which database model is being used by the server (E.g., target is <candidate> vs. <running> configuration).

There are three SIL database edit callback phases:

  1. Validate: Check the parameters no matter what database is the target

  2. Apply: The server will manipulate the database nodes as needed. The SIL usually has nothing to do in this phase unless internal resources need to be reserved.

  3. Commit or Rollback: Depending on the result of the previous phases, either the commit or the rollback callback phase will be invoked, if and when the changes are going to be finalized in the running configuration.

The SIL code is not responsible for maintaining the value tree for any database. This is done by the server.

The SIL database edit callback code is responsible for the following tasks:

  • Perform any data-model specific validation that is not already covered by a machine-readable statement, during the validation phase.

  • Reserve any data-model specific resources for the proposed new configuration content, during the apply phase.

  • Activate any data-model behavior changes based on the new configuration content, during the commit phase.

  • Release any reserved resources that were previously allocated in the apply phase, during the rollback phase.

The YANG "config" statement is used to identify configuration data nodes in NETCONF and RESTCONF.

  • If config='true':

    • the data nodes can be edited, and supports EDIT1 or EDIT2 callbacks

    • if NMDA enabled, the operational data nodes can be retrieved with GET2 callbacks

    • the data can be retrieved by a NETCONF client with these operations:

  • If config='false':

    • the data nodes cannot be edited, and supports GET1 or GET2 callbacks

    • the data can be retrieved by a NETCONF client with these operations:

Example YANG module:

container interfaces {
   list interface {
       key "name";

       leaf name {
           type string;
       }
       leaf-list untagged-ports {
           type string;
       }
       leaf speed {
           type enumeration {
               enum 10m;
               enum 100m;
               enum auto;
           }
       }
       leaf observed-speed {
           config false;
           type uint32;
       }
   }
}

leaf version {
  config false;
  type string;
}

Each callback type has its own callback API function prototype, and each callback has its own register and unregister functions that need to be used. This code is usually generated automatically by yangdump-sdk.

The following database APIs are described in this section:

Edit Callback Overview

Getting the Current Edit Transaction ID

In order to determine if an edit transaction is in progress, an API can be used. If the transaction-id returned by this function is zero, then no transaction is in progress.

ncx_transaction_id_t agt_cfg_txid_in_progress(ncx_cfg_t cfgid)

Return the ID of the current transaction ID in progress.

Parameters

cfgid -- config ID to check

Returns

txid of transaction in progress or 0 if none

Example: Get the current transaction ID for the running datastore

ncx_transaction_id_t txid = agt_cfg_txid_in_progress(NCX_CFGID_RUNNING);

Database Edit Validate Callback Phase

../_images/database_validate_phase.png

A SIL database validation phase callback function is responsible for checking all the 'description statement' sort of data model requirements that are not covered by any of the YANG machine-readable statements.

For example, if a 'user name' parameter needed to match an existing user name in /etc/passwd then the SIL validation callback would call the system APIs needed to check if the 'newval' string value matched a valid user name. The server will make sure the user name is well-formed and could be a valid user name.

Database Edit Apply Callback Phase

The callback function for this phase is called when database edits are being applied to the running configuration. The resources needed for the requested operation may be reserved at this time, if needed.

Database Edit Commit Callback Phase

This callback function for this phase is called when database edits are being committed to the running configuration. The SIL callback function is expected to finalize and apply any data-model dependent system behavior at this time.

Database Edit Rollback Callback Phase

This callback function for this phase is called when database edits are being undone, after some apply phase or commit phase callback function returned an error, or a confirmed commit operation timed out.

The SIL callback function is expected to release any resources it allocated during the apply or commit phases. Usually only the commit or the rollback function will be called for a given SIL callback, but it is possible for both to be called. For example, if the 'rollback-on-error' option is in effect, and some SIL commit callback fails after your SIL commit callback succeeds, then your SIL rollback callback may be called as well.

The server supports 2 modes of database editing callbacks. The original mode (EDIT1) is designed to invoke data node callbacks at the leaf level. This means that each altered leaf will cause a separate SIL callback. If no leaf callbacks are present, then the parent node will be invoked multiple times.

The EDIT2 mode is “list-based” or “container-based” instead. The following key aspects define EDIT2 callbacks:

  • In this mode there are no SIL callback functions expected for terminal nodes (leaf, leaf-list, anyxml).

  • The SIL callback for a container or list will be invoked, even if only child terminal nodes are being changed.

  • The parent SIL callback will be invoked only once (per phase) if multiple child nodes are being altered at once

  • The parent node for such an edit is flagged in the “undo” record as a special “edit2_merge”. The edit operation will be OP_EDITOP_MERGE, but the parent node is not being changed

  • The special “edit2_merge” type of edit will have a queue of child_undo records containing info on the child edits. For example, 1 leaf could be created, another leaf modified, and a third leaf deleted, all in the same edit request. The child_undo records provide the edit operation and values being changed.

The 'scb' parameter represents a session control block structure that is defined in the ncx/ses.h. This control block is primarily used for error reporting, as described in the example section later. However, can be used for more advanced actions. It provides access to the session specific information, such as current message input/output encoding, current session ID information, current protocol information, user name information, peer address information, etc. Note, almost all the fields in this structure should NOT be changed and accessed directly. This control block ideally should be used only for getting more information about the current session, not for alteration of any of its fields.

The 'msg' parameter represents the NETCONF Server and Client RPC Request/Reply Message Handler control block that is defined in the netconf/src/ncx/rpc.h. Similarly to SCB, this control block is primarily used for error reporting, as described in the example section later. The fields of this control block should NOT be access and changed for other purposes.

The 'cbtype' parameter represents an enumeration structure of the different server EDIT callback types that is defined in the netconf/src/agt/agt.h. This control block specifies what Phase is in the process, Validation, Apply, or Commit/Rollback, as described in the example section later.

The 'editop' parameter represents an enumeration structure of the NETCONF <edit-config> operation types that is defined in the netconf/src/ncx/op.h. This control block specifies what operation is in the process, merge, replace, delete, or other, as described in the example section later.

The 'newval' and 'curval' parameters represent data nodes that are being edited. The example section demonstrates how they can be utilized.

EDIT1 Callback

First generation edit callbacks, generated by yangdump-sdk using node-based APIs. An edit function is generated for every node, including terminal nodes (leaf, leaf-list, anyxml, anydata). An EDIT2 callback handles all the terminal nodes within the parent function instead of separate functions for each node.

New SIL or SIL-SA code should use EDIT2 callbacks instead.

EDIT1 Callback Function

The same callback template is used for EDIT1 and EDIT2 callbacks:

typedef status_t (*agt_cb_fn_t)(ses_cb_t *scb, rpc_msg_t *msg, agt_cbtyp_t cbtyp, op_editop_t editop, val_value_t *newval, val_value_t *curval)

Callback function for server object handler.

Used to provide a callback sub-mode for a specific named object

Param scb

session control block making the request

Param msg

incoming rpc_msg_t in progress

Param cbtyp

reason for the callback

Param editop

the parent edit-config operation type, which is also used for all other callbacks that operate on objects

Param newval

container object holding the proposed changes to apply to the current config, depending on the editop value. Will not be NULL.

Param curval

current container values from the <running> or <candidate> configuration, if any. Could be NULL for create and other operations.

Return

status:

EDIT1 Callback Registration and Cleanup

status_t agt_cb_register_callback(const xmlChar *modname, const xmlChar *defpath, const xmlChar *version, agt_cb_fn_t cbfn)

Register an object specific edit callback function use the same fn for all callback phases all phases will be invoked.

Parameters
  • modname -- module that defines the target object for these callback functions

  • defpath -- Xpath with default (or no) prefixes defining the object that will get the callbacks

  • version --

    exact module revision date expected if condition not met then an error will be logged (TBD: force unload of module!)

    NULL means use any version of the module

  • cbfn -- address of callback function to use for all callback phases

Returns

status

EDIT1 Callback Registration

The following example shows an EDIT1 callback registration.

#define EXAMPLE_MODNAME (const xmlChar *)"ietf-interfaces-example"
#define EXAMPLE_VERSION (const xmlChar *)"2017-01-01"
#define EXAMPLE_DEFPATH (const xmlChar *)"/if:interfaces/if:interface"

/********************************************************************
* FUNCTION interfaces_init
*
* initialize the server instrumentation library.
* Initialization Phase 1
*
*********************************************************************/
static status_t
     interfaces_init (void)
{
    status_t res =
        agt_cb_register_callback(EXAMPLE_MODNAME,
                                 EXAMPLE_DEFPATH,
                                 EXAMPLE_VERSION,
                                 edit_callback_example);
    return res;
}

EDIT1 Callback Cleanup

Use the 'agt_cb_unregister_callbacks' function to unregister all callbacks for the data node.

EDIT1 Callback Function Example

In this example, the callback code forces Rollback Phase if a new value of an “interface” list is not acceptable. Otherwise an agent can process to the next step and run device instrumentation as required.

A new list validation, in this example, is done during “commit” phase. A new value is already written to the datastore (value is getting written during apply phase) which means the server will have to reverse the edit. The server will automatically delete just created new list element from the datastore and restore the state to the initial state, to the state before the edit.


/********************************************************************
* FUNCTION  edit1_callback_example
*
* Callback function for server object handler
* Used to provide a callback sub-mode for
* a specific named object
*
* Path: /interfaces/interface
* Add object instrumentation in COMMIT phase.
*
********************************************************************/
static status_t
    edit1_callback_example (ses_cb_t *scb,
			        rpc_msg_t *msg,
			        agt_cbtyp_t cbtyp,
			        op_editop_t editop,
			        val_value_t *newval,
			        val_value_t *curval)
{
    status_t res = NO_ERR;
    val_value_t *errorval = (curval) ? curval : newval;
    const xmlChar *errorstr = (errorval) ? NCX_NT_VAL : NCX_NT_NONE;

    /* try to find a key value of the /interfaces list to validate */
    val_value_t *child_val = NULL;
    if (newval) {
        child_val =
            val_find_child(newval,
                           EXAMPLE_MODNAME,
                           (const xmlChar *)"name");

        if (child_val && typ_is_string(VAL_BTYPE(child_val))) {
            log_info("\ncallback for %s editop, test child name=%s",
                op_editop_name(editop), VAL_STR(child_val));
        }
    }

    switch (cbtyp) {
    case AGT_CB_VALIDATE:
        /* description stmt validation here */
        break;
    case AGT_CB_APPLY:
        /* database manipulation done here */
        break;
    case AGT_CB_COMMIT:
        /* device instrumentation done here */
        switch (editop) {
        case OP_EDITOP_LOAD:
            interface_enabled = TRUE;
            break;
        case OP_EDITOP_MERGE:
            break;
        case OP_EDITOP_REPLACE:
            break;
        case OP_EDITOP_CREATE:
            interface_enabled = TRUE;

             /* Force Rollback if the key value is not acceptable */
            if (newval && child_val && typ_is_string(VAL_BTYPE(child_val)) &&
                !xml_strcmp(VAL_STR(child_val), (const xmlChar *)"not-supported"))  {

                log_info("\nKey value is not supported for %s editop, name=%s",
                   op_editop_name(editop), VAL_STR(child_val));

                /* Validation failed if a key value is not supported */
                errorval = child_val;
                res = ERR_NCX_OPERATION_NOT_SUPPORTED;
            } else {
                /* Run device instrumentation here  */
            }
            break;
        case OP_EDITOP_DELETE:
            interface_enabled = FALSE;
            break;
        default:
            res = SET_ERROR(ERR_INTERNAL_VAL);
        }

        break;
    case AGT_CB_ROLLBACK:
        /* undo device instrumentation here */
        break;
    default:
        res = SET_ERROR(ERR_INTERNAL_VAL);
    }

    /* if error: set the res, errorstr, and errorval parms */
    if (res != NO_ERR) {
        agt_record_error(scb,
			     &msg->mhdr,
			     NCX_LAYER_CONTENT,
			     res,
			     NULL,
			     NCX_NT_STRING,
			     errorstr,
			     NCX_NT_VAL,
			     errorval);
    }

    return res;

} /* edit_callback_example */

The following part of the above code example is used to find the “name” key node of the edited “interface” list.

val_value_t *val_find_child(const val_value_t *parent, const xmlChar *modname, const xmlChar *childname)

Find the first instance of the specified child node.

Parameters
  • parent -- parent complex type to check

  • modname --

    module name; the first match in this module namespace will be returned

    NULL: the first match in any namespace will be returned;

  • childname -- name of child node to find

Returns

pointer to the child if found or NULL if not found

boolean typ_is_string(ncx_btype_t btyp)

Check if the base type is a simple string (not list)

Parameters

btyp -- base type enum to check

Returns

TRUE if base type is textual

FALSE if some other type

val_value_t *child_val = NULL;
if (newval) {
    child_val =
        val_find_child(newval,
                       EXAMPLE_MODNAME,
                       (const xmlChar *)"name");

    if (child_val && typ_is_string(VAL_BTYPE(child_val))) {
        log_info("\ncallback for %s editop, test child name=%s",
            op_editop_name(editop), VAL_STR(child_val));
    }
}

The 'val_find_child' API function finds the specified child node. Alternatively, 'val_find_child_fast', 'val_find_child_obj', 'val_find_child_que' API functions could be used to retrieve the desired value.

The 'typ_is_string' API function checks if the base type is a simple string to use this string later for logging. Alternatively, 'typ_is_enum', 'typ_is_number' API functions could be used to check the desired type.

The 'VAL_BTYPE()' and 'VAL_STR()' macros are used to access val_value_t structure and get the information about its base type and get string value. If the type of the value would be an integer, for example, VAL_INT32, VAL_UINT16, etc. macros could be used to retrieve the actual set value of it.

In the following part of the above code example the previously retrieved key value is validated. If the provided in the <edit-config> key value is not acceptable as specified below, then the '*res' return status will be set to ERR_NCX_OPERATION_NOT_SUPPORTED enumeration value, that would signal to record an error and rollback the <edit-config> operation.

/* Force Rollback if the key value is not acceptable */
if (newval && child_val && typ_is_string(VAL_BTYPE(child_val)) &&
    !xml_strcmp(VAL_STR(child_val), (const xmlChar *)"not-supported"))  {

    log_info("\nKey value is not supported for %s editop, name=%s",
       op_editop_name(editop), VAL_STR(child_val));

    /* Validation failed if a key value is “not-supported” */
    errorval = child_val;
    res = ERR_NCX_OPERATION_NOT_SUPPORTED;
} else {
    /* Run device instrumentation here  */
}

EDIT2 Callback

The same callback template is used for EDIT1 and EDIT callbacks. The difference is how terminal child nodes are handled. Second generation callbacks are generated by yangdump-sdk for container or list-based APIs. An edit function is generated for “parent” list and container nodes only. The terminal child nodes are handled by this callback.

Each nested container or list has its own callback. This is generated in yangdump-sdk or 'make_sil_*' scripts using the --sil-edit2 parameter. The same callback function signature as EDIT1 is used, but the registration function and procedure is different.

The server supports 2 modes of database editing callbacks. The original mode (EDIT1) is designed to invoke data node callbacks at the leaf level. This means that each altered leaf will cause a separate SIL callback. If no leaf callbacks are present, then the parent node will be invoked multiple times.

The EDIT2 mode is “list-based” or “container-based” instead.

  • In this mode there are no SIL callback functions expected for terminal nodes (leaf, leaf-list, anyxml).

  • The SIL callback for a container or list will be invoked, even if only child terminal nodes are being changed.

  • The parent SIL callback will be invoked only once (per phase) if multiple child nodes are being altered at once

  • The parent node for such an edit is flagged in the “undo” record as a special “edit2_merge”. The edit operation will be “OP_EDITOP_MERGE”, but the parent node is not being changed

  • The special “edit2_merge” type of edit will have a queue of child_undo records containing info on the child edits. For example, 1 leaf could be created, another leaf modified, and a third leaf deleted, all in the same edit request. The 'child_undo' records provide the edit operation and values being changed.

EDIT2 Callback Function

The EDIT2 callback uses the same function as the EDIT1 Callback Function.

EDIT2 Callback Initialization and Cleanup

The EDIT2 callback function is hooked into the server with the 'agt_cb_register_edit2_callback' function, described below. The SIL code generated by yangdump-pro uses this function to register a single callback function for all callback phases.

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

status_t agt_cb_register_edit2_callback(const xmlChar *modname, const xmlChar *defpath, const xmlChar *version, agt_cb_fn_t cbfn)

Register an object specific edit2 callback function.

Use the same fn for all callback phases all phases will be invoked

Only Callbacks for containers and lists are allowed in edit2 mode; Top Level Terminal Nodes are NOT SUPPORTED in edit2 mode

Parameters
  • modname -- module that defines the target object for these callback functions

  • defpath -- Xpath with default (or no) prefixes defining the object that will get the callbacks

  • version --

    exact module revision date expected if condition not met then an error will be logged (TBD: force unload of module!)

    NULL means use any version of the module

  • cbfn -- address of callback function to use for all callback phases

Returns

status

EDIT2 Callback Registration

static status_t interfaces_init (void)
{
    status_t res =
        agt_cb_register_edit2_callback(EXAMPLE_MODNAME,
                                       EXAMPLE_DEFPATH,
                                       EXAMPLE_VERSION,
                                       edit2_callback_example);
    return res;
}

EDIT2 Callback Cleanup

Use the 'agt_cb_unregister_callbacks' function to unregister all callbacks for the data node.

The unregister function needs to be called just once for a specific object. It will unregister EDIT1, EDIT2, and GET2 callbacks for the object. However, if it is called multiple times for the same object, it will return with NO errors.

All other callbacks that the object may hold should be unregistered separately.

EDIT2 Callback Function Example

The EDIT2 callback template is the same as the EDIT1 callback. The difference is that there is a queue of child edit records that may need to be accessed to reliably process the edit requests.

In the following example, EDIT2 callback code checks each 'child_undo' record in order:



/********************************************************************
* FUNCTION  edit2_callback_example
*
* Callback function for server object handler
* Used to provide a callback sub-mode for
* a specific named object
*
* Path: /interfaces/interface
* Add object instrumentation in COMMIT phase.
*
********************************************************************/
    edit2_callback_example (ses_cb_t *scb,
                    rpc_msg_t *msg,
                    agt_cbtyp_t cbtyp,
                    op_editop_t editop,
                    val_value_t *newval,
                    val_value_t *curval)
{
    status_t res = NO_ERR;
    val_value_t *errorval = (curval) ? curval : newval;

    if (LOGDEBUG) {
        log_debug("\nEnter edit2_callback_example callback for %s phase",
            agt_cbtype_name(cbtyp));
    }

    switch (cbtyp) {
    case AGT_CB_VALIDATE:
        /* description-stmt validation here */
        break;
    case AGT_CB_APPLY:
        /* database manipulation done here */
        break;
    case AGT_CB_COMMIT:
        /* device instrumentation done here */
        switch (editop) {
        case OP_EDITOP_LOAD:
            break;
        case OP_EDITOP_MERGE:
            /* the edit is not really on this node; need to get
             * each child_undo record to get the real edited nodes
             * and the edited operations
             */
            agt_cfg_transaction_t *txcb = RPC_MSG_TXCB(msg);
            agt_cfg_undo_rec_t *child_edit =
                agt_cfg_first_child_edit(txcb, newval, curval);

            while (child_edit) {
                op_editop_t child_editop = OP_EDITOP_NONE;
                val_value_t *child_newval = NULL;
                val_value_t *child_curval = NULL;
                xmlChar *newval_str = NULL;
                xmlChar *curval_str = NULL;

                agt_cfg_child_edit_fields(child_edit,
                                          &child_editop,
                                          &child_newval,
                                          &child_curval);

                if (child_newval) {
                    newval_str = val_make_sprintf_string(child_newval);
                    if (newval_str == NULL) {
                        return ERR_INTERNAL_MEM;
                    }
                }
                if (child_curval) {
                    curval_str = val_make_sprintf_string(child_curval);
                    if (curval_str == NULL) {
                        m__free(newval_str);
                        return ERR_INTERNAL_MEM;
                    }
                }

                log_info("\n        %s: editop=%s newval=%s curval=%s",
                      child_newval ? VAL_NAME(child_newval) : NCX_EL_NULL,
                      child_editop ? op_editop_name(child_editop) : NCX_EL_NONE,
                      child_newval ? newval_str : NCX_EL_NULL,
                      child_curval ? curval_str : NCX_EL_NULL);


		   /* Force Rollback if the child value is not acceptable */
                if (child_newval &&
                    !xml_strcmp(VAL_NAME(child_newval),
                                (const xmlChar *)"untagged-ports") &&
                    !xml_strcmp(VAL_STR(child_newval),
                                (const xmlChar *)"not-supported")) {

                    res = ERR_NCX_OPERATION_NOT_SUPPORTED;
                    m__free(newval_str);
                    m__free(curval_str);
                    break;
                }

                /**** process child edits here ****/


                m__free(newval_str);
                m__free(curval_str);

                child_edit = agt_cfg_next_child_edit(child_edit);
            }

            break;
        case OP_EDITOP_REPLACE:
        case OP_EDITOP_CREATE:
            /* the edit is on this list node and the child editop
             * can be treated the same as the parent (even if different)
             * the val_value_t child nodes can be accessed and there
             * are no child_undo records to use
             */
            val_value_t *child_newval = NULL;
            child_newval =
                val_find_child(newval,
                               EXAMPLE_MODNAME,
                               (const xmlChar *)"untagged-ports");

                val_value_t *leaflist_val = child_newval;
                while (leaflist_val) {

                    /**** process child leaf-list edits here ****/

                    leaflist_val = val_next_child_same(leaflist_val);
                }

                /**** process other child edits here if needed ****/

            break;
        case OP_EDITOP_DELETE:
            break;
        default:
            res = SET_ERROR(ERR_INTERNAL_VAL);
        }
        break;
    case AGT_CB_ROLLBACK:
        /* undo device instrumentation here */
        break;
    default:
        res = SET_ERROR(ERR_INTERNAL_VAL);
    }

    if (res != NO_ERR) {
        agt_record_error(scb,
                         &msg->mhdr,
                         NCX_LAYER_CONTENT,
                         res,
                         NULL,
                         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
                         errorval,
                         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
                         errorval);
    }

    return res;

} /* edit2_callback_example */
const xmlChar *agt_cbtype_name(agt_cbtyp_t cbtyp)

Get the string for the server callback phase.

Parameters

cbtyp -- callback type enum

Returns

const string for this enum

Accessing Child Node Edits

If the operation is “merge”, the edit is not really on the container or list node. The callback needs to get each child_undo record to get the real edited nodes and the edit operations.

If the parent operation is “merge” and the actual edit is on the node with default value. The edited node has a “default” YANG statement, then the actual child edit operation will always be “merge”. The difference will be in the newval, curval values.

create default node

new non-default value

default value

modify default node

new non-default or set back to default value

old non-default value

delete default node

default value

old non-default value

The following code demonstrates how to loop through the child undo records and retrieve the actual operation and the actual child nodes.

case OP_EDITOP_MERGE:
    agt_cfg_transaction_t *txcb = RPC_MSG_TXCB(msg);
    agt_cfg_undo_rec_t *child_edit =
        agt_cfg_first_child_edit(txcb, newval, curval);

    while (child_edit) {
        op_editop_t child_editop = OP_EDITOP_NONE;
        val_value_t *child_newval = NULL;
        val_value_t *child_curval = NULL;
        xmlChar *newval_str = NULL;
        xmlChar *curval_str = NULL;

        agt_cfg_child_edit_fields(child_edit,
                                  &child_editop,
                                  &child_newval,
                                  &child_curval);

        if (child_newval) {
            newval_str = val_make_sprintf_string(child_newval);
            if (newval_str == NULL) {
                return ERR_INTERNAL_MEM;
            }
        }
        if (child_curval) {
            curval_str = val_make_sprintf_string(child_curval);
            if (curval_str == NULL) {
                m__free(newval_str);
                return ERR_INTERNAL_MEM;
            }
        }

        log_info("\n        %s: editop=%s newval=%s curval=%s",
              child_newval ? VAL_NAME(child_newval) : NCX_EL_NULL,
              child_editop ? op_editop_name(child_editop) : NCX_EL_NONE,
              child_newval ? newval_str : NCX_EL_NULL,
              child_curval ? curval_str : NCX_EL_NULL);


           /* Force Rollback if the child value is not acceptable */
        if (child_newval &&
            !xml_strcmp(VAL_NAME(child_newval),
                        (const xmlChar *)"untagged-ports") &&
            !xml_strcmp(VAL_STR(child_newval),
                        (const xmlChar *)"not-supported")) {

            res = ERR_NCX_OPERATION_NOT_SUPPORTED;
            m__free(newval_str);
            m__free(curval_str);
            break;
        }

        /**** process child edits here ****/


        m__free(newval_str);
        m__free(curval_str);

        child_edit = agt_cfg_next_child_edit(child_edit);
    }
    break;
agt_cfg_undo_rec_t *agt_cfg_first_child_edit(agt_cfg_transaction_t *txcb, val_value_t *newnode, val_value_t *curnode)

Get the first child node edit record for a given transaction.

Parameters
  • txcb -- transaction control block to clean

  • newnode -- new value node passed to callback

  • curnode -- current value node passed to callback

Returns

pointer to the undo edit record for the first child node edit

NULL if none found

agt_cfg_undo_rec_t *agt_cfg_next_child_edit(agt_cfg_undo_rec_t *curedit)

Get the next child node edit record for a given transaction.

Parameters

curedit -- pointer to the current child edit

Returns

pointer to the undo edit record for the next child node edit

NULL if none found

void agt_cfg_child_edit_fields(agt_cfg_undo_rec_t *child_undo, op_editop_t *editop, val_value_t **newval, val_value_t **curval)

Get the child edit fields from the undo record.

Parameters
  • child_undo -- child undo record that was returned

  • editop -- address of return editop

  • newval -- address of return new value

  • curval -- address of return current value

Return values
  • *editop -- return editop

  • *newval -- return new value

  • *curval -- return current value

The following code illustrates how to loop through the children undo records in order to retrieve the actual operation and the actual edited children:

agt_cfg_undo_rec_t *child_edit =
    agt_cfg_first_child_edit(txcb, newval, curval);

while (child_edit) {
    op_editop_t child_editop = OP_EDITOP_NONE;
    val_value_t *child_newval = NULL;
    val_value_t *child_curval = NULL;

    agt_cfg_child_edit_fields(child_edit,
                              &child_editop,
                              &child_newval,
                              &child_curval);

     /**** process child edits here ****/


    child_edit = agt_cfg_next_child_edit(child_edit);
}

EDIT2 Callback For a Create Operation

val_value_t *val_next_child_same(val_value_t *curchild)

Get the next instance of the corresponding child node.

Parameters

curchild -- child type to find next instance of

Returns

pointer to the next child of same type or NULL if none

The following code shows how to access the 'untagged-ports' leaf-list child nodes that are getting created along with their “interface” parent:

case OP_EDITOP_CREATE:
    val_value_t *child_newval = NULL;
    child_newval =
        val_find_child(newval,
                       EXAMPLE_MODNAME,
                       (const xmlChar *)"untagged-ports");

        val_value_t *leaflist_val = child_newval;
        while (leaflist_val) {

            /**** process child leaf-list edits here ****/

            leaflist_val = val_next_child_same(leaflist_val);
        }

        /**** process other child edits here if needed ****/

    break;
  • If an edit operation creates a new or modifies an existing data node, or its children, the EDIT2 callback will be invoked and run.

  • If the edit operation creates a new list entry, the operation will be “create” and any children that are getting created along with its parent should be treated as regular nodes and should be accessed the same way as for EDIT1 callback.

  • If the operation is “merge”, the actual operation and edit is on children nodes, so the child edits should be checked and applied.

SIL-SA EDIT2 Callbacks

The SIL-SA EDIT2 callback usage is the same as the EDIT2 callback except EDIT2 MERGE handling. The difference is only in the children edits access APIs.

There are SIL-SA EDIT2 mode specific high-level transaction access and management utilities in sil-sa/sil_sa.h. These functions access the lower-level functions to provide simpler functions for common transaction management tasks.

The following table highlights available functions and SIL vs SIL-SA difference:

SIL-SA Function

SIL Function

Description

sil_sa_first_child_edit

agt_cfg_first_child_edit

Get the first child node edit record for a given transaction.

sil_sa_next_child_edit

agt_cfg_next_child_edit

Get the next child node edit record for a given transaction.

sil_sa_child_edit_fields

agt_cfg_child_edit_fields

Get the child edit fields from the specified undo record.

sil_sa_child_edit_t *sil_sa_first_child_edit(rpc_msg_t *msg)

Get the first child edit from the transaction control block.

SIL-SA EDIT2 MODE

SIL-SA Analogue agt_cfg_first_child_edit() API. Get the first child node edit record

Parameters

msg -- rpc msg to use to find keys

Returns

pointer to the first child edit

sil_sa_child_edit_t *sil_sa_next_child_edit(sil_sa_child_edit_t *curedit)

Get the next child edit from the transaction control block.

SIL-SA EDIT2 MODE

SIL-SA Analogue agt_cfg_next_child_edit() API. Get the next child node edit

Parameters

curedit -- pointer to the current child edit

Returns

pointer to next child node edit

NULL if none found

void sil_sa_child_edit_fields(sil_sa_child_edit_t *child_edit, op_editop_t *editop, val_value_t **newval, val_value_t **curval)

Get the child edit fields.

SIL-SA EDIT2 MODE

SIL-SA Analogue agt_cfg_child_edit_fields() API. Get the child edit fields from the child_edit record

Parameters
  • child_edit -- child_edit record that was returned from the server

  • editop -- address of return editop

  • newval -- address of return new value

  • curval -- address of return current value

Return values
  • *editop -- return editop

  • *newval -- return new value

  • *curval -- return current value

SIL-SA EDIT2 Callback Function Example

In the following example, SIL-SA EDIT2 callback code gets each 'child edit' record in order to retrieve the real edited nodes.



/********************************************************************
* FUNCTION  silsa_edit2_callback_example
*
* SIL-SA EDIT2 Callback function for server object handler
* Used to provide a callback sub-mode for
* a specific named object
*
* Path: /interfaces/interface
* Add object instrumentation in COMMIT phase.
*
********************************************************************/
    silsa_edit2_callback_example (ses_cb_t *scb,
                                  rpc_msg_t *msg,
                                  agt_cbtyp_t cbtyp,
                                  op_editop_t editop,
                                  val_value_t *newval,
                                  val_value_t *curval)
{
    status_t res = NO_ERR;
    val_value_t *errorval = (curval) ? curval : newval;

    if (LOGDEBUG) {
        log_debug("\nEnter silsa_edit2_callback_example callback for %s phase",
            agt_cbtype_name(cbtyp));
    }

    switch (cbtyp) {
    case AGT_CB_VALIDATE:
        /* description-stmt validation here */
        break;
    case AGT_CB_APPLY:
        /* database manipulation done here */
        break;
    case AGT_CB_COMMIT:
        /* device instrumentation done here */
        switch (editop) {
        case OP_EDITOP_LOAD:
            break;
        case OP_EDITOP_MERGE:
            /* the edit is not really on this node; need to get
             * each child_undo record to get the real edited nodes
             * and the edited operations
             */
            sil_sa_child_edit_t *child_edit = sil_sa_first_child_edit(msg);
            while (child_edit) {

                op_editop_t child_editop = OP_EDITOP_NONE;
                val_value_t *child_newval = NULL;
                val_value_t *child_curval = NULL;
                xmlChar *newval_str = NULL;
                xmlChar *curval_str = NULL;

                sil_sa_child_edit_fields(child_edit,
                                         &child_editop,
                                         &child_newval,
                                         &child_curval);
                if (child_newval) {
                    newval_str = val_make_sprintf_string(child_newval);
                    if (newval_str == NULL) {
                        return;
                    }
                }
                if (child_curval) {
                    curval_str = val_make_sprintf_string(child_curval);
                    if (curval_str == NULL) {
                        if (newval_str) {
                            m__free(newval_str);
                        }
                        return;
                    }
                }

                log_debug("\n        %s: editop=%s newval=%s curval=%s",
                          child_newval ? VAL_NAME(child_newval) : NCX_EL_NULL,
                          child_editop ? op_editop_name(child_editop) : NCX_EL_NONE,
                          child_newval ? newval_str : NCX_EL_NULL,
                          child_curval ? curval_str : NCX_EL_NULL);

                m__free(newval_str);
                m__free(curval_str);

                child_edit = sil_sa_next_child_edit(child_edit);
            }

            break;
        case OP_EDITOP_REPLACE:
        case OP_EDITOP_CREATE:
            /* the edit is on this list node and the child editop
             * can be treated the same as the parent (even if different)
             * the val_value_t child nodes can be accessed and there
             * are no child_undo records to use
             */
            val_value_t *child_newval = NULL;
            child_newval =
                val_find_child(newval,
                               EXAMPLE_MODNAME,
                               (const xmlChar *)"untagged-ports");

                val_value_t *leaflist_val = child_newval;
                while (leaflist_val) {

                    /**** process child leaf-list edits here ****/

                    leaflist_val = val_next_child_same(leaflist_val);
                }

                /**** process other child edits here if needed ****/

            break;
        case OP_EDITOP_DELETE:
            break;
        default:
            res = SET_ERROR(ERR_INTERNAL_VAL);
        }
        break;
    case AGT_CB_ROLLBACK:
        /* undo device instrumentation here */
        break;
    default:
        res = SET_ERROR(ERR_INTERNAL_VAL);
    }

    if (res != NO_ERR) {
        agt_record_error(scb,
                         &msg->mhdr,
                         NCX_LAYER_CONTENT,
                         res,
                         NULL,
                         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
                         errorval,
                         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
                         errorval);
    }

    return res;

} /* silsa_edit2_callback_example */

EDIT2 KnowledgeBase FAQs

Database Template

Every NETCONF database has a common template control block, and common set of access functions.

NETCONF databases are not separate entities like separate SQL databases. Each NETCONF database is conceptually the same database, but in different states:

  • candidate: A complete configuration that may contain changes that have not been applied yet. This is only available if the :candidate capability is advertised by the server. The value tree for this database contains only configuration data nodes.

  • running: The complete current server configuration. This is available on every NETCONF server. The value tree for this database contains configuration data nodes and non-configuration nodes created and maintained by the server. The server will maintain read-only nodes when <edit-config>, <copy-config>, or :ref"<commit> operations are performed on the running configuration. SIL code should not alter the data nodes within a configuration directly. This work is handled by the server. SIL callback code should only alter its own data structures, if needed.

  • startup: A complete configuration that will be used upon the next reboot of the device. This is only available if the :startup capability is advertised by the server. The value tree for this database contains only configuration data nodes.

NETCONF also recognized external files via the <url> parameter, if the :url capability is advertised by the server. These databases will be supported in a future release of the server. The NETCONF standard does not require that these external databases support the same set of protocol operations as the standard databases, listed above. A client application can reliably copy from and to an external database, but editing and filtered retrieval may not be supported.

cfg_template_t Structure

The following typedef is used to define a NETCONF database template:

struct cfg_template_t

struct representing 1 configuration database

Public Members

ncx_cfg_t cfg_id

config ID: Internal configuration ID assigned to this configuration.

cfg_location_t cfg_loc

config location: Enumeration identifying the configuration source location.

cfg_state_t cfg_state

config state: Current internal configuration state.

ncx_transaction_id_t last_txid

last edit transaction ID

ncx_transaction_id_t cur_txid

current edit transaction ID

time_t last_modified

last modified timestamp

xmlChar *name

datastore name string

xmlChar *src_url

source URL: URL for use with 'cfg_loc' to identify the configuration source.

xmlChar lock_time[TSTAMP_MIN_SIZE]

Date and time string when the configuration was last locked.

xmlChar last_ch_time[TSTAMP_MIN_SIZE]

Date and time string when the configuration was last changed.

uint32 flags

Internal configuration flags.

Do not use directly.

ses_id_t locked_by

Session ID that owns the global configuration lock, if the database is currently locked.

cfg_source_t lock_src
dlq_hdr_t load_errQ

Queue of rpc_err_rec_t structures that represent any <rpc-error> records that were generated when the configuration was loaded, if any.

dlq_hdr_t plockQ
boolean rwlock_initialized

RWLOCK for this config - used by multiple reader/writer threads according to RWLOCK rules: basically, max one writer, multiple readers, ordered by request time.

pthread_rwlock_t rwlock

PTHREADS=1 only: rwlock data.

ses_id_t rw_wrlocked_by

PTHREADS=1 only: Single write lock holder, if any.

ses_id_t rw_rdlocked_by

Most recent read lock holder only, used for debugging if the background thread locks the cfg for fill_candidate_from_running or other reason then the SID will be zero.

boolean rw_wrlocked

PTHREADS=1 only: TRUE if currently write-locked.

boolean rw_rdlocked

PTHREADS=1 only: TRUE if currently read-locked.

boolean wrlock_pending

TRUE if the wrlock is active and set.

val_value_t *root

datastore root value.

btyp == NCX_BT_CONTAINER. The root of the value tree representing the entire database.

boolean fake_candidate

TRUE if this is YANG-PATCH request fake candidate template.

cfg_template_t Access Functions

The file ncx/cfg.h contains some high-level database access functions that may be of interest to SIL callback functions for custom RPC operations. All database access details are handled by the server if the database edit callback functions are used (associated with a particular object node supported by the server). The following table highlights the most commonly used functions. Refer to the H file for a complete definition of each API function.

cfg_new_template

Create a new configuration database.

cfg_free_template

Free a configuration database.

cfg_get_state

Get the current internal database state.

cfg_get_config

Get a configuration database template pointer, from a configuration name string.

cfg_get_config_name

Get the config name from its ID.

cfg_get_config_id

Get a configuration database template pointer, from a configuration ID.

cfg_set_target

Set the CFG_FL_TARGET flag in the specified config.

cfg_fill_candidate_from_running

Fill the <candidate> config with the config contents of the <running> config.

cfg_fill_candidate_from_startup

Fill the <candidate> config with the config contents of the <startup> config.

cfg_fill_candidate_from_inline

Fill the candidate database from an internal value tree data structure.

cfg_get_dirty_flag

Returns TRUE if the database has changes in it that have not been saved yet. This applies to the candidate and running databases at this time.

cfg_clear_dirty_flag

Clear the cfg dirty flag upon request.

cfg_clear_running_dirty_flag

Clear the running dirty flag when it is saved to NV-storage or loaded into running from startup.

cfg_clear_candidate_dirty_flag

Clear the candidate dirty flag when it is saved to NV-storage or loaded into running from startup.

cfg_get_dirty_flag

Get the config dirty flag value.

cfg_ok_to_lock

Check if the database could be successfully locked by a specific session.

cfg_ok_to_unlock

Check if the database could be successfully unlocked by a specific session.

cfg_ok_to_read

Check if the database is in a state where read operations are allowed.

cfg_ok_to_write

Check if the database could be successfully written by a specific session. Checks the global configuration lock, if any is set.

cfg_is_global_locked

Returns TRUE if the database is locked right now with a global lock.

cfg_get_global_lock_info

Get some information about the current global lock on the database.

cfg_is_partial_locked

Check if the specified config has any active partial locks.

cfg_add_partial_lock

Add a partial lock the specified config. This will not really have an effect unless the CFG_FL_TARGET flag in the specified config is also set . For global lock only.

cfg_find_partial_lock

Find a partial lock in the specified config.

cfg_first_partial_lock

Get the first partial lock in the specified config.

cfg_next_partial_lock

Get the next partial lock in the specified config.

cfg_delete_partial_lock

Remove a partial lock from the specified config.

cfg_ok_to_partial_lock

Check if the specified config can be locked right now for partial lock only.

cfg_get_root

Get the config root for the specified config.

cfg_lock

Get a global lock on the database.

cfg_unlock

Release the global lock on the database.

cfg_unlock_ex

Release the global lock on the database. Do not always force a remove changes. This is needed for the <unload> operation which locks the datastores while deleting data nodes and schema nodes for the module being unloaded.

cfg_release_locks

Release all locks on all databases.

cfg_release_partial_locks

Release any configuration locks held by the specified session.

cfg_get_lock_list

Get a list of all the locks held by a session.

cfg_update_last_ch_time

Update the last-modified time-stamp.

cfg_update_last_txid

Update the last good transaction ID.

cfg_update_stamps

Update the last-modified and last-txid stamps.

cfg_get_last_ch_time

Get the last-modified time-stamp.

cfg_get_last_txid

Get the last good transaction ID.

cfg_sprintf_etag

Write the Entity Tag for the datastore to the specified buffer.

cfg_get_startup_filespec

Get the filespec string for the XML file to save the running database.

Database Access Utilities

Finding Data Nodes with XPath

The internal XPath API can be used to access configuration data nodes. The data should not be altered. This API should be considered read-only.

Step 1) Create the XPath parser control block
xpath_pcb_t *xpath_new_pcb(const xmlChar *xpathstr, xpath_getvar_fn_t getvar_fn)

malloc a new XPath parser control block

xpathstr is allowed to be NULL, otherwise a strdup will be made and exprstr will be set

Create and initialize an XPath parser control block

Parameters
  • xpathstr --

    XPath expression string to save (a copy will be made)

    NULL if this step should be skipped

  • getvar_fn --

    callback function to retirieve an XPath variable binding

    NULL if no variables are used

Returns

pointer to malloced struct, NULL if malloc error

xpath_pcb_t *pcb =
    xpath_new_pcb((const xmlChar *)"/interfaces/interface", NULL);
if (pcb == NULL) {
    return ERR_INTERNAL_MEM;
}
Step 2) Evaluate the XPath expression
xpath_result_t *xpath1_eval_expr(xpath_pcb_t *pcb, val_value_t *val, val_value_t *docroot, boolean logerrors, boolean configonly, status_t *res)

Evaluate an XPath expression use if the prefixes are YANG: must/when.

Evaluate the expression and get the expression nodeset result

Parameters
  • pcb -- XPath parser control block to use

  • val -- start context node for value of current()

  • docroot -- ptr to cfg->root or top of rpc/rpc-replay/notif tree

  • logerrors --

    TRUE if log_error and ncx_print_errormsg should be used to log XPath errors and warnings

    FALSE if internal error info should be recorded in the

    xpath_result_t struct instead

  • configonly -- config mode

  • res -- address of return status

Return values

*res -- is set to the return status

Returns

malloced result struct with expr result NULL if no result produced (see *res for reason)

  • 'docroot' is usually a datastore root like 'cfg->cfg_root'

  • context node can be an interior node, but can also be the 'docroot'

xpath_result_t *result =
    xpath1_eval_expr(pcb,
                     root,
                     root,
                     FALSE, // logerrors
                     TRUE,  // configonly
                     &res);
Step 3) Iterate through the node-set result
xpath_resnode_t *xpath_get_first_resnode(xpath_result_t *result)

Get the first result in the renodeQ from a result struct.

Parameters

result -- result struct to check

Returns

pointer to resnode or NULL if some error

xpath_resnode_t *xpath_get_next_resnode(xpath_resnode_t *resnode)

Get the next result in the renodeQ from a result struct.

Parameters

resnode -- current result node to get next from

Returns

pointer to next resnode or NULL if some error

val_value_t *xpath_first_resnode_valptr(xpath_resnode_t *resnode)

Get the first result in the renodeQ from a result struct.

Parameters

resnode -- result node struct to check

Returns

pointer to value node resnode or NULL if some error

val_value_t *xpath_next_resnode_valptr(xpath_resnode_t *resnode, val_value_t *valptr)

Get the next node val pointer from a result node struct.

Parameters
  • resnode -- pointer of result node struct

  • valptr -- current value pointer to get next for

Returns

the next val pointer or NULL if some error

Note that XPath result nodes may reference a value node header, so it is safest to iterate through the value instances in the resnode. If there is only one entry this code will still work:

 xpath_resnode_t *resnode = xpath_get_first_resnode(result);
 for (; resnode; resnode = xpath_get_next_resnode(resnode)) {

     val_value_t *valnode = xpath_first_resnode_valptr(resnode);
     for (; valnode; valnode = xpath_next_resnode_valptr(resnode, valnode)) {

         // do something with valnode.... e.g dump the value if debug2
         if (LOGDEBUG2) {
             log_debug2("\nGot resnode value:\n");
             val_dump_value(valnode, 0, DBG2);
         }
    }
}

Determining if a Value Node is Set

A val_value_t structure is usually allocated with val_new_value() and then initialized with val_init_from_template().

In order to determine if the value has been set by a client, and not set by default or simply initialized, use the API function 'val_is_value_set'.

boolean val_is_value_set(val_value_t *val)

Check if a value has been set by a client It has to be initialized and not set by default to return true.

Parameters

val -- value to check

Returns

true if value has been set

false if has not been set, or set to default, or the code cannot tell if the value is more than initialized

Determining if the SIL Callback is the Deepest for the Edit

A datastore edit includes data for a subtree of configuration data. It is possible that multiple SIL callbacks will be invoked for the edit operation.

The function 'agt_val_silcall_is_deepest' from file agt/agt_val_silcall.h can be used to determine if the current callback is the deepest SIL invocation for the edit.

boolean agt_val_silcall_is_deepest(rpc_msg_t *msg)

Check if the current nested SIL callback is at the deepest level for the current edit.

Example

In the example above, the code is generated as EDIT2 code so there are no callbacks for leafs

This function would return TRUE for list 'tlist3' and FALSE for all ancestor node callbacks (tnest, tlist, list2)

FALSE if the current SIL callback is not at the deepest level for the edit that contains the silcall record. There is at least one nested_silcall involved in the current edit that is at a child level compared to the current node being called

FALSE if there is no current silcall set

Parameters

msg -- RPC message to check

Returns

TRUE if the current SIL callback is at the deepest level for the edit that contains the silcall record

In-Transaction Callbacks

In-transaction APIs are used in netconfd-pro to allow an application to update the configuration data and perform additional actions while the transaction is in progress.

The following diagram illustrates what callbacks are invoked and when to expect them to be invoked during the transaction.

../_images/silsa_hooks.png

To manipulate the datastore contents in the same transaction:

  • Set Hook

  • Post Set Hook

To perform additional validations, security checks or any other actions with the target datastore in the same transaction:

  • Transaction Hook

  • Start Transaction

  • Complete Transaction

To perform additional actions and manipulations with the target datastore in the same transaction during the <commit> operation, there are Commit Completeness callbacks:

  • Startup Hook

  • Validate Complete

  • Apply Complete

  • Commit Complete

  • Rollback Complete

Note

In-Transaction callbacks are not automatically generated by yangdump-pro code generation. They must be added manually.

The following SIL callback APIs are supported:

Set Hook

  • Similar to the EDIT1 or EDIT2 callbacks, except this callback is only invoked once at the start of the edit for a particular data node.

  • This callback can alter the data in the current edit and also add new edits to the current transaction.

  • This callback will be invoked before EDIT1 or EDIT2 callbacks for the same object.

Post Set Hook

  • Similar to the Set Hook callback except that it is invoked when an object is modified but AFTER EDIT callback is done for the same object.

  • If --target=candidate is used, this callback will be invoked when changes are done to the <candidate> datastore

  • If --target=running is used, this callback will be invoked when changes are done to the <running> datastore, invoked at the start of the transaction on the <running> datastore.

  • This callback will be invoked AFTER a EDIT1 or EDIT2 callback for the same object.

Transaction Hook

  • Similar to the Set Hook callback except this callback is invoked just before the data is committed to the running datastore.

  • This callback will be invoked after EDIT1 or EDIT2 callbacks for the same object.

Transaction Start

  • This callback that is intended to provide access to the transaction control block and its parameters right before the transaction is started.

Transaction Complete

  • This callback is intended to provide access to the transaction control block and its parameters right after the transaction is completed.

  • Called for every transaction completion, not just transactions that complete without errors.

Validate Complete

  • This callback is intended to allow access to the <running> and <candidate> datastores (equivalent to completion of validation phase during <commit>).

Apply Complete

  • This callback is intended to allow access to the <running> and <candidate> datastores (equivalent to completion of apply phase during <commit>).

Commit Complete

  • This callback is a user/system callback that is invoked when the <commit> operation completes without errors or the internal <replay-config> operation completes without errors.

Rollback Complete

  • This callback is a user/system callback that is invoked after and if the Rollback Phase has been processed during the <commit> operation.

Set Order Hook

  • This callback allows the SIL Priority to be set for data nodes in the EDIT transaction.

  • Set the secondary SIL priority for instances of the same list object that is being set in the same edit.

  • This allows the instance SIL callback order to be set in addition to the object priority.

Startup Hook

  • This callback function is invoked before any changes are done to the <startup> datastore.

  • It is invoked only if the <startup> capability is enabled.

Dynamic Default Hook

  • This is a user/system callback that can be used to set up dynamic default value to the non-default leafy nodes during edit operations.

  • It is invoked when the server checks if the node has any defaults for leafy nodes without static YANG "default" statements

The server supports SIL-SA callbacks for the following In-Transaction APIs:

SA Transaction Start

  • This callback is intended to provide access to the edit transaction right before the transaction is started.

  • The server does not provide the transaction control block to SIL-SA callbacks

  • Instead it passes the transaction ID for reference and some information about the transaction, such as datastore target and the callback phase.

SA Transaction Complete

  • This callback is intended to provide access to the edit transaction right after the transaction is completed.

  • Called for every transaction completion, not just transactions that complete without errors.

  • The server does not provide transaction control block

  • Instead it passes only the transaction ID for reference.

SA Set Hook

  • This callback is intended to alter the data in the current edit and or add new edits to the current transaction.

  • The transaction control block is not available

  • Instead the server provides all necessary information as a set of parameters.

SA Post Set Hook

  • This callback is intended to alter the data in the current edit and or add new edits to the current transaction after the EDIT callbacks for the same node.

  • The transaction control block is not available

  • Instead the server provides all necessary information as a set of parameters.

SA Transaction Hook

  • This callback is intended to provide a validation point for specific node(s).

  • The server does not provide the transaction control block

  • Instead it passes the transaction ID for reference and some information about the transaction.

SA Validate Complete

  • This callback is intended to provide a validation point after the Validate Phase is completed.

  • The server does not provide running and candidate values.

  • Instead it passes the transaction ID for reference and Get Data API can be used to run additional validation if required.

SA Apply Complete

  • This callback is intended to provide a validation point after the Apply Phase is completed.

  • The server does not provide running and candidate values.

  • Instead it passes the transaction ID for reference and Get Data API can be used to run additional validation if required.

SA Commit Complete

  • This callback is intended to provide a validation point after the Commit Phase is completed.

  • The server does not provide running and candidate values.

  • Instead it passes the transaction ID for reference and Get Data API can be used to run additional validation if required.

SA Rollback Complete

  • This callback is intended to provide a validation point after the Rollback Phase is completed.

  • The server does not provide running and candidate values.

  • Instead it passes the transaction ID for reference and Get Data API can be used to run additional validation if required.

Add Edit and Get Data APIs

The following API functions can be called from the Set Hook or Post Set Hook callbacks:

  • Add Edit: Add an edit operation to the current transaction

    • SIL: agt_val_add_edit

    • SIL-SA: sil_sa_add_edit

  • Get Data: Retrieve an instance of a data node from the server.

    • SIL: agt_val_get_data

    • SIL-SA: sil_sa_get_data

Callback Invocation Order

Assume all the possible callbacks are registered and the edit is complex as possible. The possible invocation order may look as follows, processing on the <candidate> datastore (before the <commit>):

  1. Transaction Start

  2. Set Order Hook

  3. Set Hook and then Set Order Hook for the added edit if any

  4. SIL Callbacks (only during Validation Phase)

  5. Post Set Hook and then Set Order Hook for the added edit if any

  6. SIL Callbacks for added edits (only during Validation Phase)

  7. Transaction Complete

The possible invocation order during the <commit> operation may look as follows, processing on the <running> datastore, after this operation.

  1. Transaction Start

  2. Set Order Hook

  3. SIL Callbacks (Validate Phase)

  4. Validate Complete

  5. SIL Callbacks (Apply Phase)

  6. Apply Complete

  7. SIL Callbacks (Commit Phase)

  8. Transaction Hook

  9. Commit/Rollback Complete

  10. Transaction Complete

The <commit> operation is not available if the default target is set to <running>. Thus, the possible invocation order during the edit operation may look as follows, processing on the running datastore only (the same invocation order will be during Load, Reload and Restart operations):

  1. Transaction Start

  2. Set Order Hook

  3. Set Hook and then Set Order Hook for the added edit if any

  4. SIL Callbacks (Validate Phase)

  5. Post Set Hook and then Set Order Hook for the added edit if any

  6. SIL Callbacks for added edits (Validate Phase)

  7. SIL Callbacks (Apply Phase)

  8. SIL Callbacks (Commit Phase)

  9. Transaction Hook

  10. Transaction Complete

The possible invocation order during the 'validate' operation may look as follows, processing on the <running> datastore only:

  1. Transaction Start

  2. Set Order Hook

  3. SIL Callbacks (Validate Phase)

  4. Transaction Complete

Set Hook, Post Set Hook and Transaction Hook callbacks will not be invoked during an explicit <validate> operation and (for Set-Hook) in case the --sil-validate-candidate parameter is set to "true".

Set Hook

A Set Hook is a function that is invoked within the transaction when an object is modified. It is invoked when changes are done to the target configuration datastore, depending on the --target parameter.

This callback will be invoked before a EDIT1 or EDIT2 callback for the same object. Set Hook can be invoked during the <load-config> operation, however, it is not allowed to add new edits. Any Add Edit API calls during this operation will be ignored.

Note

Manipulation of datastore nodes is only allowed for Set Hook and Post Set Hook callbacks. If any other In-Transaction callbacks invoke the Add Edit API the operation will be ignored.

Set Hook Callback

The following function template definition is used for Set Hook callback functions:

typedef status_t (*agt_cb_hook_t)(ses_cb_t *scb, rpc_msg_t *msg, agt_cfg_transaction_t *txcb, op_editop_t editop, val_value_t *newval, val_value_t *curval)

Typedef of the agt_cb_hook_t callback.

Callback function for server object handler Used to provide a callback for a specific named object

Set Hook: Similar to the EDIT1 or EDIT2 callbacks, except this callback is only invoked once at the start of the edit for a particular data node. This callback can alter the data in the current edit and also add new edits to the current transaction. This callback will be invoked before a EDIT1 or EDIT2 callback for the same object.

Post Set Hook: Is a postponed Set Hook callback analogous function that is invoked within the transaction when an object is modified but AFTER EDIT callback is done for the same object. This callback will be invoked AFTER a EDIT1 or EDIT2 callback for the same object.

Transaction Hook: Similar to a Set Hook except this callback is invoked just before the data is committed to the running datastore. This callback will be invoked after a EDIT1 or EDIT2 callbacks for the same object.

Param scb

session control block making the request

Param msg

incoming rpc_msg_t in progress

Param txcb

transaction control block in progress

Param editop

edit operation enumeration for the node being edited

Param newval

container object holding the proposed changes to apply to the current config, depending on the editop value. Will not be NULL.

Param curval

current container values from the <running> or <candidate> configuration, if any. Could be NULL for create and other operations.

Return

status

Set Hook Callback Initialization and Cleanup

  • A Set Hook database edit callback function is registered with the 'agt_cb_hook_register' function, described below.

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

status_t agt_cb_hook_register(const xmlChar *defpath, agt_hook_fmt_t format, agt_hook_type_t type, agt_cb_hook_t cbfn)

Register an object specific Hook callback function.

Parameters
  • defpath -- Xpath with default (or no) prefixes defining the object that will get the callback

  • format -- different hook formats dictates specific hook functionality

  • type -- different hook types dictates hook invocation point

  • cbfn -- address of callback function to use for all callback phases

Returns

the status of the operation

Example Usage:

static status_t interfaces_init (void)
{
    /* Register an object specific Set Hook callback */
    status_t res =
        agt_cb_hook_register((const xmlChar *)"/if:trigger",
                             AGT_HOOKFMT_NODE,
                             AGT_HOOK_TYPE_SETHOOK,
                             sethook_callback_edit);

    return res;
}

The 'format' parameter is used to specify how Set Hook callbacks will be invoked. There are two options available for this parameter:

  • AGT_HOOKFMT_NODE: Set the type of the callback to this value if You want to make sure that the callback will be invoked only when you modify the node that registered the callback but not any of its children.

  • AGT_HOOKFMT_SUBTREE: If the format is set to this value, the callback will be invoked if you edit children as well.

The 'type' parameter is used to set the type of callback. There are two options available for this parameter, either Set Hook or Transaction Hook callback:

  • AGT_HOOK_TYPE_SETHOOK: Set the type of the callback to this value if You want to register Set Hook callback.

  • AGT_HOOK_TYPE_TRANSACTION: Set the type of the callback to this value if You want to register Transaction Hook callback.

void agt_cb_hook_unregister(const xmlChar *defpath)

Unregister a Hook callback.

This function unregisters a Hook callback.

Parameters

defpath -- Xpath with default (or no) prefixes defining the object that will get the callback

Example Usage:

 void interfaces_cleanup (void)
 {
     // ...

     agt_cb_hook_unregister((const xmlChar *)"/if:trigger");

     // ...
 }

Set Hook Callback and Add Edit API Function Examples

The following YANG module will be used in this example:

container interfaces {
   list interface {
       key "name";

       leaf name {
           type string;
       }
       leaf speed {
           type enumeration {
               enum 10m;
               enum 100m;
               enum auto;
           }
       }
       leaf hook-node {
           type uint32;
       }
       container state {
           leaf admin-state {
               type boolean;
           }
       }
   }

  leaf status {
    type string;
  }
}

...

leaf trigger {
  type string;
}

Add a New Node

The Set Hook callback was registered for the /trigger leaf element. Whenever the node is edited, the callback function will be invoked and additional data can be added to the edit transaction.

In this example, an extra “interface” list entry will be generated, with key value equal to “vlan1” when a “trigger” node is getting edited with a specific value equal to “add-edit”.

The callback function may look as follows:

/********************************************************************
* FUNCTION  sethook_callback_edit
*
* Callback function for server object handler
* Used to provide a callback for a specific named object
*
* Set Hook:
*   trigger: edit /if:trigger
*   add_edit:
*       add nodes: populate 1 list entry with name=vlan1
*
*           path: /if:interfaces/if:interface[if:name=vlan1]
*
*********************************************************************/
static status_t
    sethook_callback_edit (ses_cb_t *scb,
                          rpc_msg_t *msg,
                          agt_cfg_transaction_t *txcb,
                          op_editop_t editop,
                          val_value_t  *newval,
                          val_value_t  *curval)
{
    log_debug("\nEnter SET Hook callback");

    status_t res = NO_ERR;
    val_value_t *errorval = (curval) ? curval : newval;

    const xmlChar *defpath =
        (const xmlChar *)"/if:interfaces";

     switch (editop) {
    case OP_EDITOP_LOAD:
        break;
    case OP_EDITOP_MERGE:
    case OP_EDITOP_REPLACE:
    case OP_EDITOP_CREATE:
        /* add a new edit if the "/if:trigger" value is "add-edit" */
        if (newval &&
            !xml_strcmp(VAL_STR(newval), (const xmlChar *)"add-edit")) {

            /* find object template of the desired node */
            obj_template_t *targobj =
                      ncx_match_any_object_ex(EXAMPLE_MODNAME,
                                              (const xmlChar *)"interfaces",
                                              FALSE,
                                              NCX_MATCH_FIRST,
                                              FALSE,
                                              &res);
            if (!targobj) {
                return ERR_NCX_INVALID_VALUE;
            }

           /* create edit_value container value */
            val_value_t *editval = val_new_value();
            if (editval == NULL) {
                return ERR_INTERNAL_MEM;
            }
            val_init_from_template(editval, targobj);

            /* malloce and construct list value, for more
             * examples refer to libhooks-test/src/hooks-test.c library
		*/
            uint32 key = 11;
            val_value_t *list_value = create_list_entry(VAL_OBJ(editval),
                                                        key,
                                                        &res);
            if (!list_value) {
                val_free_value(editval);
                return res;
            }

            /* add a new list entry */
            res = val_child_add(list_value, editval);
            if (res != NO_ERR) {
                val_free_value(list_value);
            } else {
                /* add a new edit, MERGE on defpath with 1 new list entry */
                res = agt_val_add_edit(scb,
                                       msg,
                                       txcb,
                                       defpath,
                                       editval,
                                       OP_EDITOP_MERGE);
            }

            /* clean up the editval */
            val_free_value(editval);
        }
        break;
    case OP_EDITOP_DELETE:
        break;
    default:
        res = SET_ERROR(ERR_INTERNAL_VAL);
    }

    if (res != NO_ERR) {
        agt_record_error(scb,
                         &msg->mhdr,
                         NCX_LAYER_CONTENT,
                         res,
                         NULL,
                         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
                         errorval,
                         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
                         errorval);
    }

    return res;

}  /* sethook_callback_edit */

Refer to create_list_entry for an example of the list entry creation.

Example Response Message:

<rpc-reply message-id="101"
  xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <interfaces xmlns="http://yumaworks.com/ns/interfaces">
    <interface>
      <name>vlan1</name>
      <observed-speed>1000</observed-speed>
    </interface>
  </interfaces>
  <trigger>add-edit</trigger>
</rpc-reply>

Delete a Node

In this example, the /if:interfaces container and all its children will be deleted when the “trigger” leaf node is getting deleted.

As a result, the server will delete the whole /if:interfaces container if 'trigger' value is set to 'delete-all' and a client attempts to delete the /trigger node.


/********************************************************************
* FUNCTION  sethook_callback_edit
*
* Callback function for server object handler
* Used to provide a callback for a specific named object
*
* Set Hook:
*   trigger: delete /trigger
*   add_edit:
*       delete nodes: delete the whole container
*
*           path: /if:interfaces
*
*********************************************************************/
static status_t
    sethook_callback_edit (ses_cb_t *scb,
                          rpc_msg_t *msg,
                          agt_cfg_transaction_t *txcb,
                          op_editop_t editop,
                          val_value_t  *newval,
                          val_value_t  *curval)
{
    log_debug("\nEnter SET Hook callback");

    status_t res = NO_ERR;
    val_value_t *errorval = (curval) ? curval : newval;

    const xmlChar *defpath =
        (const xmlChar *)"/if:interfaces";

     switch (editop) {
    case OP_EDITOP_LOAD:
        break;
    case OP_EDITOP_MERGE:
    case OP_EDITOP_REPLACE:
    case OP_EDITOP_CREATE:
        break;
    case OP_EDITOP_DELETE:
        /* delete the interfaces container if the curval of 'trigger' node is
         * "delete-all"
         */
	 if (curval &&
            !xml_strcmp(VAL_STR(curval), (const xmlChar *)"delete-all")) {

            res = agt_val_add_edit(scb,
                                   msg,
                                   txcb,
                                   defpath,
                                   NULL, // editval
                                   editop);
        }
        break;
    default:
        res = SET_ERROR(ERR_INTERNAL_VAL);
    }

    if (res != NO_ERR) {
        agt_record_error(scb,
                         &msg->mhdr,
                         NCX_LAYER_CONTENT,
                         res,
                         NULL,
                         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
                         errorval,
                         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
                         errorval);
    }

    return res;

}  /* sethook_callback_edit */

Set Hook Callback and Add Edit Extended API Function Examples

This section will demonstrate how to insert or move a specific list or leaf-list entry using the 'add_edit_ex' API function. This API is only allowed to be used within the Set Hook callbacks as described in the previous sections.

Note

The server does not support the insert and move operation on the nodes that are being modified in the same transaction at the same time.

The following YANG module will be used for this example:

module example {
  namespace "http://netconfcentral.org/ns/example";
  prefix "ex";

  revision 2019-01-01 {
      description "Initial revision.";
  }

  leaf insert-list-check {
    type string;
  }

  leaf insert-leaf-list-check {
    type string;
  }

  leaf move-list-check {
    type string;
  }

  leaf move-leaf-list-check {
    type string;
  }

  list hook-list-test {
    key name;
    ordered-by user;

    leaf name {
      type string;
    }
    leaf a2 {
      type string;
    }
    leaf b2 {
      type uint32;
      default 5;
    }
  }

  leaf-list hook-leaf-list-test {
    ordered-by user;
    type string;
  }

}

Insert New List Nodes

Assume the Set Hook callback for the “insert-list-check” leaf element was registered. Whenever the node /ex:insert-list-check is edited, the callback function will be called and additional specific entry can be inserted or moved within the datastore.

Refer to the Add Edit Extended API section for details on the "agt_val_add_edit_ex" API function.

In this example, new “hook-list-test” list entries will be inserted when a “insert-list-check” node is modified.


#define HOOKS_TEST_MOD (const xmlChar *)"example"
#define HOOKS_TEST_REV (const xmlChar *)"2019-01-01"

/********************************************************************
* FUNCTION create_list_entry
*
* Make a list entry based on the key
*
* INPUTS:
*   res = return status
*
* RETURNS:
*    val_value_t of listval entry if no error
*    else NULL
*
*********************************************************************/
static val_value_t *
    create_list_entry (obj_template_t *list_obj,
                       const xmlChar *keyname,
                       const xmlChar *keystr,
                       status_t *res)
{
    /* add all the /container/list nodes */
    val_value_t *list_value = val_new_value();
    if (!list_value) {
        *res = ERR_NCX_INVALID_VALUE;
        return NULL;
    }
    val_init_from_template(list_value, list_obj);

    /* make key leaf entry */
    val_value_t *child =
        agt_make_leaf(list_obj,
                      keyname,
                      keystr,
                      res);
    if (!child) {
        val_free_value(list_value);
        *res = ERR_NCX_INVALID_VALUE;
        return NULL;
    }
    val_add_child(child, list_value);

    /* generate the internal index Q chain */
    *res = val_gen_index_chain(list_obj, list_value);
    if (*res != NO_ERR) {
        log_error("\nError: could not generate index chain (%s)",
            get_error_string(*res));
        val_free_value(list_value);
        return NULL;
    }

    return list_value;

} /* create_list_entry */


/********************************************************************
* FUNCTION hooks_sethook_edit_insert
*
* Callback function for server object handler
* Used to provide a callback for a specific named object
*
* INPUTS:
*   scb == session control block making the request
*   msg == incoming rpc_msg_t in progress
*   txcb == transaction control block in progress
*   editop == edit operation enumeration for the node being edited
*   newval == container object holding the proposed changes to
*           apply to the current config, depending on
*           the editop value. Will not be NULL.
*   curval == current container values from the <running>
*           or <candidate> configuration, if any. Could be NULL
*           for create and other operations.
*
* RETURNS:
*    status
*********************************************************************/
static status_t
    hooks_sethook_edit_insert (ses_cb_t *scb,
                               rpc_msg_t *msg,
                               agt_cfg_transaction_t *txcb,
                               op_editop_t editop,
                               val_value_t  *newval,
                               val_value_t  *curval)
{
    status_t res = NO_ERR;
    val_value_t *errorval = (curval) ? curval : newval;

    /* look for a top-node data object */
    obj_template_t *targobj =
        ncx_match_any_object_ex(HOOKS_TEST_MOD,
                                (const xmlChar *)"hook-list-test",
                                FALSE,
                                NCX_MATCH_FIRST,
                                FALSE,
                                &res);
    if (!targobj) {
        return ERR_NCX_NOT_FOUND;
    }

    val_value_t *editval = NULL;
    const xmlChar *defpath = NULL;
    const xmlChar *edit_operation = NULL;
    const xmlChar *insert_point = NULL;
    const xmlChar *insert_where = NULL;

    switch (editop) {
    case OP_EDITOP_LOAD:
        break;
    case OP_EDITOP_MERGE:
    case OP_EDITOP_REPLACE:
    case OP_EDITOP_CREATE:
        /* INSERT KEY2 AFTER KEY1 */
        if (newval && !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger"))  {

            editval =
                create_list_entry(targobj,
                                  (const xmlChar *)"name",
                                  (const xmlChar *)"key2",
                                  &res);
            if (!editval) {
                return res;
            }

            /* defpath specified target */
            defpath = (const xmlChar *)"/ex:hook-list-test[name='key2']";
            edit_operation = (const xmlChar *)"insert";
            insert_where = (const xmlChar *)"after";

            /* assume we already have key1 in the datastore */
            insert_point = (const xmlChar *)"/ex:hook-list-test[name='key1']";

        /* INSERT KEY3 BEFORE KEY1 */
        } else if (newval &&
                   !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger2")) {

            editval =
                create_list_entry(targobj,
                                  (const xmlChar *)"name",
                                  (const xmlChar *)"key3",
                                  &res);
            if (!editval) {
                return res;
            }

            /* defpath specified target */
            defpath = (const xmlChar *)"/ex:hook-list-test[name='key3']";
            edit_operation = (const xmlChar *)"insert";
            insert_where = (const xmlChar *)"before";
            insert_point = (const xmlChar *)"/ex:hook-list-test[name='key1']";

        /* INSERT KEY4 LAST */
        } else if (newval &&
                   !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger3")) {

            editval =
                create_list_entry(targobj,
                                  (const xmlChar *)"name",
                                  (const xmlChar *)"key4",
                                  &res);
            if (!editval) {
                return res;
            }

            /* defpath specified target */
            defpath = (const xmlChar *)"/ex:hook-list-test[name='key4']";
            edit_operation = (const xmlChar *)"insert";
            insert_where = (const xmlChar *)"last";
            insert_point = NULL;

        /* INSERT KEY5 FIRST */
        } else if (newval &&
                   !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger4")) {

            editval =
                create_list_entry(targobj,
                                  (const xmlChar *)"name",
                                  (const xmlChar *)"key5",
                                  &res);
            if (!editval) {
                return res;
            }

            /* defpath specified target */
            defpath = (const xmlChar *)"/ex:hook-list-test[name='key5']";
            edit_operation = (const xmlChar *)"insert";
            insert_where = (const xmlChar *)"first";
            insert_point = NULL;


        /* Negative test, missing insert point */
        } else if (newval &&
                   !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger-fail")) {

            editval =
                create_list_entry(targobj,
                                  (const xmlChar *)"name",
                                  (const xmlChar *)"NEW",
                                  &res);
            if (!editval) {
                return res;
            }

            /* defpath specified target */
            defpath = (const xmlChar *)"/ex:hook-list-test[name='NEW']";
            edit_operation = (const xmlChar *)"insert";
            insert_where = (const xmlChar *)"before";
            insert_point = (const xmlChar *)"/ex:hook-list-test[name='MISSING']";

        /* Negative test, where is missing */
        } else if (newval &&
                   !xml_strcmp(VAL_STR(newval), (const xmlChar *)"trigger-fail2")) {

            editval =
                create_list_entry(targobj,
                                  (const xmlChar *)"name",
                                  (const xmlChar *)"NEW",
                                  &res);
            if (!editval) {
                return res;
            }

            /* defpath specified target */
            defpath = (const xmlChar *)"/ex:hook-list-test[name='NEW']";
            edit_operation = (const xmlChar *)"insert";
            insert_where = NULL;
            insert_point = (const xmlChar *)"/ex:hook-list-test[name='key2']";

        } else {
            /* nothing to add or modify. Specific criteria not met */
            break;
        }


        /* add a new edit on defpath and populate new entry */
        if (res == NO_ERR) {
            res = agt_val_add_edit_ex(scb,
                                      msg,
                                      txcb,
                                      defpath,
                                      editval,
                                      edit_operation,
                                      insert_where,
                                      insert_point);
        }

        if (editval) {
            /* clean up the editval */
            val_free_value(editval);
        }

        /* Final Result should look as follows:
         *
         *   example:hook-list-test  key5 {   inserted 'first' by Set-Hook
         *     name key5
         *   }
         *   example:hook-list-test  key3 {   inserted 'before' by Set-Hook
         *     name key3
         *   }
         *   example:hook-list-test  key1 {   created by edit-config
         *     name key1
         *   }
         *   example:hook-list-test  key2 {   inserted 'after' by Set-Hook
         *     name key2
         *   }
         *   example:hook-list-test  key4 {   inserted 'last' by Set-Hook
         *     name key4
         *   }
         *
         */

        break;
    case OP_EDITOP_DELETE:
        break;
    default:
        res = SET_ERROR(ERR_INTERNAL_VAL);
    }

    if (res != NO_ERR) {
        agt_record_error(scb,
                         &msg->mhdr,
                         NCX_LAYER_CONTENT,
                         res,
                         NULL,
                         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
                         errorval,
                         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
                         errorval);
    }
    return res;

}  /* hooks_sethook_edit_insert */


/********************************************************************
* FUNCTION init_example_test
*
* Initialize the example module callbacks
*
*********************************************************************/
static status_t
    init_example_test (void)
{
    status_t res = NO_ERR;

    res = agt_cb_hook_register((const xmlChar *)"/ex:insert-list-check",
                               AGT_HOOKFMT_NODE,
                               AGT_HOOK_TYPE_SETHOOK,
                               hooks_sethook_edit_insert);
    if (res != NO_ERR) {
        return res;
    }

    return NO_ERR;

} /* init_example_test */


/********************************************************************
* FUNCTION cleanup_example_test
*
* Cleanup the example module callbacks
*
*********************************************************************/
static void
    cleanup_example_test (void)
{
    agt_cb_hook_unregister((const xmlChar *)"/ex:insert-list-check");

}   /* cleanup_example_test */

SIL-SA Set Hook Callback

The Set Hook API is available for SIL-SA functions.

The following function template definition is used for Set Hook callback for SIL-SA functions:

typedef status_t (*agt_cb_sa_hook_t)(ses_cb_t *scb, rpc_msg_t *msg, op_editop_t editop, val_value_t *newval, val_value_t *curval, const xmlChar *transaction_id, boolean isvalidate, boolean isload, boolean isrunning)

Typedef of the SIL-SA version of agt_cb_hook_t callback.

Callback function for server object handler Used to provide a callback for a specific named object

Set Hook: Similar to the EDIT1 or EDIT2 callbacks, except this callback is only invoked once at the start of the edit for a particular data node. This callback can alter the data in the current edit and also add new edits to the current transaction. This callback will be invoked before a EDIT1 or EDIT2 callback for the same object.

Post Set Hook: Is a postponed Set Hook callback analogous function that is invoked within the transaction when an object is modified but AFTER EDIT callback is done for the same object. This callback will be invoked AFTER a EDIT1 or EDIT2 callback for the same object.

Transaction Hook: Similar to a Set Hook except this callback is invoked just before the data is committed to the running datastore. This callback will be invoked after a EDIT1 or EDIT2 callbacks for the same object.

Param scb

session control block making the request

Param msg

incoming rpc_msg_t in progress

Param editop

edit operation enumeration for the node being edited

Param newval

container object holding the proposed changes to apply to the current config, depending on the editop value. Will not be NULL.

Param curval

current container values from the <running> or <candidate> configuration, if any. Could be NULL for create and other operations.

Param transaction_id

transaction ID of the transaction control block in progress

Param isvalidate

TRUE if this Transaction is for <validate> operation

Param isload

TRUE if this Transaction is for a Load operation

Param isrunning

TRUE if this Transaction is for the the running datastore

Return

status

SIL-SA Set Hook Callback Initialization and Cleanup

The 'Set Hook SIL-SA' callback function is registered with the 'agt_cb_sa_hook_register' function.

  • The 'format' parameter is used to specify how Set Hook callbacks will be invoked.

    • There are two options available:

      • AGT_HOOKFMT_NODE: Invoked only when the node that registered the callback is modified.

      • AGT_HOOKFMT_SUBTREE: Invoked if any node in the subtree is modified.

  • The 'type' parameter is used to set the type of callback.

    • There are two options available:

      • AGT_HOOK_TYPE_SETHOOK: Register a Set Hook callback.

      • AGT_HOOK_TYPE_TRANSACTION: Register Transaction Hook callback.

status_t agt_cb_sa_hook_register(const xmlChar *defpath, agt_hook_fmt_t format, agt_hook_type_t type, agt_cb_sa_hook_t cbfn)

Register an object specific Hook callback function.

Parameters
  • defpath -- Xpath with default (or no) prefixes defining the object that will get the callback

  • format -- different hook formats dictates specific hook functionality

  • type -- different hook types dictates hook invocation point

  • cbfn -- address of SIL-SA callback function to use

Returns

the status of the operation

Note

Set Hook callbacks will not be invoked if any of the following conditions are true:

Example initialization function:

static status_t interfaces_init (void)
{
    /* Register an object specific Set Hook callback */
    status_t res =
       agt_cb_sa_hook_register((const xmlChar *)"/if:trigger",
                               AGT_HOOKFMT_NODE,
                               AGT_HOOK_TYPE_SETHOOK,
                               sethook_callback_edit);

    // ...
}
void agt_cb_sa_hook_unregister(const xmlChar *defpath)

Unregister a SIL-SA Hook SIL-SA callback.

This function unregisters a Hook callback.

Parameters

defpath -- Xpath with default (or no) prefixes defining the object that will get the callback

Example cleanup function:

 void interfaces_cleanup (void)
 {
     // ...

     agt_cb_sa_hook_unregister((const xmlChar *)"/if:trigger");

     // ...
 }

Important Differences Between SIL and SIL-SA

The following table illustrates most important difference between SIL and SIL-SA version of the Set Hook callback:

Difference

SIL

SIL-SA

Type Definition

agt_cb_hook_t

agt_cb_sa_hook_t

Registration Function

ag t_cb_hook_register()

agt_c b_sa_hook_register()

Clean Up Function

agt_ cb_hook_unregister()

agt_cb_ sa_hook_unregister()

Add Edit API

agt_val_add_edit()

sil_sa_add_edit()

Get Data API

agt_val_get_data()

sil_sa_get_data()

Transaction Control Block

Available

NOT available

SIL-SA Set Hook Callback and Add Edit API Function Examples

The following YANG module is used for this example:

module silsa-sethook-example {
  namespace "http://netconfcentral.org/ns/silsa-sethook-example";
  prefix "sa-sethook-ex";

  revision 2020-08-18 {
    description "Initial revision.";
  }

  container interfaces {
    list interface {
      key "name";

      leaf name {
        type string;
      }
      leaf speed {
        type enumeration {
          enum 10m;
          enum 100m;
          enum auto;
        }
      }
      leaf hook-node {
        type uint32;
      }
      container state {
        leaf admin-state {
          type boolean;
        }
      }
    }

    leaf status {
      type string;
    }
  }

  leaf trigger {
    type string;
  }
}

Note

The Set Hook callback is not part of the auto-generated code the stub SIL-SA code must be created and modified manually.

Assume a Set Hook callback is registered for the “trigger” leaf node. In this example, an extra “interface” list entry with key value equal to “vlan1” will be generated, when a “trigger” node is getting edited with as specific value equal to “add-edit”:


/********************************************************************
* FUNCTION  sethook_callback
*
* Callback function for server object handler
* Used to provide a callback for a specific named object
*
* Set Hook:
*   trigger: edit /trigger
*   add_edit:
*       add nodes: populate 1 list entry with name=vlan1
*
*           path: /interfaces/interface[name=vlan1]
*
*********************************************************************/
static status_t
    sethook_callback (ses_cb_t *scb,
                      rpc_msg_t *msg,
                      op_editop_t editop,
                      val_value_t *newval,
                      val_value_t *curval,
                      const xmlChar *transaction_id,
                      boolean isvalidate,
                      boolean isload,
                      boolean isrunning)
{
    status_t res = NO_ERR;
    val_value_t *errorval = (curval) ? curval : newval;

    const xmlChar *user = sil_sa_get_username();
    const xmlChar *client_addr = sil_sa_get_client_addr();

    if (LOGDEBUG2) {
        log_debug2("\n\n********************************************");

        print_callback_info(errorval,
                            AGT_CB_VALIDATE,
                            editop,
                            (const xmlChar *)"SETHOOK");

        log_debug2("\ntransaction_id -- %s", transaction_id);
        log_debug2("\nuser_id -- %s", user);
        log_debug2("\nclient_addr -- %s", client_addr);
        log_debug2("\nisvalidate -- %s",
            isvalidate ? NCX_EL_TRUE : NCX_EL_FALSE);
        log_debug2("\nisload -- %s",
            isload ? NCX_EL_TRUE : NCX_EL_FALSE);
        log_debug2("\nisrunning -- %s",
            isrunning ? NCX_EL_TRUE : NCX_EL_FALSE);
        log_debug2("\n********************************************\n\n");
    }

    const xmlChar *defpath =
        (const xmlChar *)"/sa-sethook-ex:interfaces";

    switch (editop) {
    case OP_EDITOP_LOAD:
        break;
    case OP_EDITOP_MERGE:
    case OP_EDITOP_REPLACE:
    case OP_EDITOP_CREATE:
        /* add a new edit if the "/trigger" value is "add-edit" */
        if (newval &&
            !xml_strcmp(VAL_STR(newval), (const xmlChar *)"add-edit")) {

            /* find object template of the desired node */
            obj_template_t *targobj =
                ncx_find_object(silsa_sethook_example_mod,
                                (const xmlChar *)"interfaces");
            if (!targobj) {
                return ERR_NCX_INVALID_VALUE;
            }

           /* create edit_value container value */
            val_value_t *editval = val_new_value();
            if (editval == NULL) {
                return ERR_INTERNAL_MEM;
            }
            val_init_from_template(editval, targobj);

            /* malloc and construct list value */
            val_value_t *list_value =
                  create_list_entry(VAL_OBJ(editval),
                                    (const xmlChar *)"vlan1",
                                    &res);
            if (!list_value) {
                val_free_value(editval);
                return res;
            }

            /* add a new list entry */
            res = val_child_add(list_value, editval);
            if (res != NO_ERR) {
                val_free_value(list_value);
            } else {
                /* add a new edit, MERGE on defpath with 1 new list entry */
                const xmlChar *edit_operation = (const xmlChar *)"merge";
                const xmlChar *insert_point = NULL;
                const xmlChar *insert_where = NULL;
                boolean skip_cb = FALSE;

                res =
                    sil_sa_add_edit(defpath,
                                    editval,
                                    edit_operation,
                                    insert_where,
                                    insert_point,
                                    skip_cb);
            }

            /* clean up the editval */
            val_free_value(editval);
        }
        break;
    case OP_EDITOP_DELETE:
        break;
    default:
        res = SET_ERROR(ERR_INTERNAL_VAL);
    }

    if (res != NO_ERR) {
        agt_record_error(scb,
                         &msg->mhdr,
                         NCX_LAYER_CONTENT,
                         res,
                         NULL,
                         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
                         errorval,
                         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
                         errorval);
    }

    return res;

}  /* sethook_callback */

An <edit-config> request that will trigger desired Set Hook actions may look as follows:

<edit-config>
  <target>
    <candidate/>
  </target>
  <default-operation>merge</default-operation>
  <test-option>set</test-option>
  <config>
    <trigger xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"
      nc:operation="create"
      xmlns="http://netconfcentral.org/ns/silsa-sethook-example">add-edit</trigger>
  </config>
</edit-config>

As a result, the callback is invoked and additionally creates a new interface /interfaces/interface[name=value].

Example Response with the created data:

<data>
 <interfaces xmlns="http://netconfcentral.org/ns/silsa-sethook-example">
  <interface>
   <name>vlan1</name>
   <hook-node>1000</hook-node>
  </interface>
 </interfaces>
 <trigger xmlns="http://netconfcentral.org/ns/silsa-sethook-example">add-edit</trigger>
</data>

Post Set Hook

A Post Set Hook callback is a postponed Set Hook callback that is invoked within the transaction when an object is modified but AFTER the EDIT callback is done for the same object.

Note

This callback will not be invoked in case the --sil-root-check-first parameter is set to TRUE when the server is run with --target=running .

The same callback template is used for Set Hook and Post Set Hook. Refer to the Set Hook Callback section for details.

Post Set Hook Callback Initialization and Cleanup

A Post Set Hook callback function is registered with the 'agt_cb_post_sethook_register' function, described below. The registration is done during the Initialization Phase 1 before the startup configuration is loaded into the running configuration database.

status_t agt_cb_post_sethook_register(const xmlChar *defpath, agt_cb_hook_t cbfn)

Register an object specific Post Set Hook callback function.

Parameters
  • defpath -- Xpath with default (or no) prefixes defining the object that will get the callback

  • cbfn -- address of callback function to use

Returns

the status of the operation

Example Usage:

static status_t interfaces_init (void)
{
    // ...
    /* Register an object specific Post Set Hook callback */
    res =
       agt_cb_post_sethook_register((const xmlChar *)"/if:trigger",
                                    sethook_callback_edit);

    // ...
}

The same "unregister" function is used as a Set Hook. Refer to the Set Hook Callback Initialization and Cleanup section for details.

Example Usage:

void interfaces_cleanup (void)
{
    // ...
    agt_cb_hook_unregister((const xmlChar *)"/if:trigger");

    // ...
}

SIL-SA Post Set Hook Callback

A SIL-SA version of the Post Set Hook callback is supported. This callback serves the same purpose as the Post Set Hook callback.

SIL-SA Post Set Hook Callback Initialization and Cleanup

A Post Set Hook callback function is hooked into the server with the 'agt_cb_sa_post_sethook_register' function, described below. The registration is done during the Initialization Phase 1 before the startup configuration is loaded into the running configuration database.

status_t agt_cb_sa_post_sethook_register(const xmlChar *defpath, agt_cb_sa_hook_t cbfn)

Register an object specific SIL-SA Post Set Hook callback function.

Parameters
  • defpath -- Xpath with default (or no) prefixes defining the object that will get the callback

  • cbfn -- address of SIL-SA callback function to use

Returns

the status of the operation

Example Usage:

static status_t  interfaces_init (void)
{
    // ...
    /* Register an object specific Post Set Hook callback */
    res =
       agt_cb_sa_post_sethook_register((const xmlChar *)"/if:trigger",
                                       post_sethook_callback_edit);

    // ...
}
void agt_cb_sa_post_sethook_unregister(const xmlChar *defpath)

Unregister a SIL-SA Post Set Hook callback.

Parameters

defpath -- Xpath with default (or no) prefixes defining the object that will get the callback unregister

Example Usage:

void interfaces_cleanup (void)
{
    // ...

    agt_cb_sa_hook_unregister((const xmlChar *)"/if:trigger");

    // ...
}

Hooks Callback Interaction with EDIT2 Callbacks

In case there are multiple Set-Hook and Post Set Hook callbacks registered and the callbacks add several extra edits to the transaction with help of Add Edit API the server will have the following interaction with added edits and normal edits that are coming from PDU.

Assume a Set-Hook, Post Set Hook, Set-Order-Hook, Transaction-Hook and EDIT2 callback are all registered for the same object. Any time the 'interface' list is edited the server invokes the Set-Hook and Post Set Hook callback, which adds an extra 'interface' entry, in addition to the regular edit.

The following callback invocation order is expected in this case:

Hook Callback for <candidate> Datastore

Edit on candidate datastore:

  1. Transaction Start

  2. Set Order Hook for /if:interface[name=vlan1]

  3. Set Hook for /if:interface[name=vlan1] to add /if:interface[name=vlan2]

  4. Set Order Hook for /if:interface[name=vlan2]

  5. SIL Callback (Validate Phase) for /if:interface[name=vlan1]

  6. Post Set Hook for /if:interface[name=vlan1] to add /if:interface[name=vlan3]

  7. SIL Callback (Validate Phase) for /if:interface[name=vlan2]

  8. SIL Callback (Validate Phase) for /if:interface[name=vlan3]

  9. Transaction Complete

Edit on running datastore (after <commit>):

  1. Transaction Start

  2. SIL Callback (Validate Phase) for /if:interface[name=vlan1]

  3. SIL Callback (Validate Phase) for /if:interface[name=vlan2]

  4. SIL Callback (Validate Phase) for /if:interface[name=vlan3]

  5. Validate Complete

  6. SIL Callback (Apply Phase) for /if:interface[name=vlan1]

  7. SIL Callback (Apply Phase) for /if:interface[name=vlan2]

  8. SIL Callback (Apply Phase) for /if:interface[name=vlan3]

  9. Apply Complete

  10. SIL Callback (Commit Phase) for /if:interface[name=vlan1]

  11. SIL Callback (Commit Phase) for /if:interface[name=vlan2]

  12. SIL Callback (Commit Phase) for /if:interface[name=vlan3]

  13. Transaction Hook for /if:interface[name=vlan1]

  14. Transaction Hook for /if:interface[name=vlan2]

  15. Transaction Hook for /if:interface[name=vlan3]

  16. Commit/Rollback Complete

  17. Transaction Complete

Hook Callback for <running> Datastore

Assume the same scenario but the default target in this case is set to <running>. The callbacks invocation order is expected to be:

  1. Transaction Start

  2. Set Order Hook for /if:interface[name=vlan1]

  3. Set Hook for /if:interface[name=vlan1] to add /if:interface[name=vlan2]

  4. Set Order Hook for /if:interface[name=vlan2]

  5. SIL Callback (Validate Phase) for /if:interface[name=vlan1]

  6. Post Set Hook for /if:interface[name=vlan1] to add /if:interface[name=vlan3]

  7. SIL Callback (Validate Phase) for /if:interface[name=vlan2]

  8. SIL Callback (Validate Phase) for /if:interface[name=vlan3]

  9. SIL Callback (Apply Phase) for /if:interface[name=vlan1]

  10. SIL Callback (Apply Phase) for /if:interface[name=vlan2]

  11. SIL Callback (Apply Phase) for /if:interface[name=vlan3]

  12. SIL Callback (Commit Phase) for /if:interface[name=vlan1]

  13. SIL Callback (Commit Phase) for /if:interface[name=vlan2]

  14. SIL Callback (Commit Phase) for /if:interface[name=vlan3]

  15. Transaction Hook for /if:interface[name=vlan1]

  16. Transaction Hook for /if:interface[name=vlan2]

  17. Transaction Hook for /if:interface[name=vlan3]

  18. Transaction Complete

In case default target is set to <running> there is an option to control callback invocation for added edits with help of a new "agt_val_add_edit_max" API. Refer to the Add Edit Maximum API section for details.

If the 'skip_c'* parameter is set to TRUE then the server will not invoke any callback for added edits. In this case the callback order will look as follows. Assume the same scenario but the default target in this case is set to <running>.

  1. Transaction Start

  2. Set Order Hook for /if:interface[name=vlan1]

  3. Set Hook for /if:interface[name=vlan1] to add /if:interface[name=vlan2]

  4. Set Order Hook for /if:interface[name=vlan2]

  5. SIL Callback (Validate Phase) for /if:interface[name=vlan1]

  6. Post Set Hook for /if:interface[name=vlan1] to add /if:interface[name=vlan3]

  7. SIL Callback (Apply Phase) for /if:interface[name=vlan1]

  8. SIL Callback (Commit Phase) for /if:interface[name=vlan1]

  9. Transaction Hook for /if:interface[name=vlan1]

  10. Transaction Complete

Note

The skip_cb parameter can be used only when the default target is <running>. The server will always invoke callbacks for added edits in case the target is <candidate>.

Transaction Hook

A Transaction Hook is a function that is invoked within the transaction when an object is modified. The Transaction Hook is similar to the Set Hook except this callback is invoked just before the data is committed to the running datastore. This callback will be invoked after EDIT1 or EDIT2 callbacks for the same object.

Transaction Hook Callback

The same callback template is used for Set Hook and Transaction Hook. Refer to the Set Hook Callback section for details.

Transaction Hook Callback Initialization and Cleanup

A Transaction Hook callback function is registered and unregistered with the 'agt_cb_hook_register' and 'agt_cb_hook_unregister; functions.

Refer to the Set Hook Callback Initialization and Cleanup section for details.

The following example shows how a Transaction Hook may be registered:

static status_t interfaces_init (void)
{
    // ...
    /* Register an object specific Transaction Hook callback */
    res =
       agt_cb_hook_register((const xmlChar *)"/if:trigger",
                                  AGT_HOOKFMT_NODE,
                                  AGT_HOOK_TYPE_TRANSACTION,
                                  trans_callback_edit);

     // ...
}

Transaction Hook Callback Function Example

Whenever a /if:interfaces node is edited, the callback is invoked and additionally validates the /if:interfaces/if:status node. Based on this validation, the operation can be denied.


/********************************************************************
* FUNCTION transhook_callback
*
* Callback function for server object handler
* Used to provide a callback for a specific named object
*
* Transaction-Hook:
*   trigger: DELETE /interface
*   effect:
*       - if testval node exist the CREATE operation will be denied
*       - if testval is ‘deny-delete’, the operation will be denied
*
********************************************************************/
static status_t
    transhook_callback (ses_cb_t *scb,
                        rpc_msg_t *msg,
                        agt_cfg_transaction_t *txcb,
                        op_editop_t editop,
                        val_value_t  *newval,
                        val_value_t  *curval)
{
    log_debug("\nEnter Transaction-Hook callback");

    status_t res = NO_ERR;
    status_t res2 = NO_ERR;
    val_value_t *errorval = (curval) ? curval : newval;

    const xmlChar *defpath =
	(const xmlChar *)"/if:interfaces/if:status";

    /* find a test node and validate its value */
    val_value_t *testval =
        agt_val_get_data(txcb->cfg_id,
	        	     defpath,
                         &res2);

    switch (editop) {
    case OP_EDITOP_LOAD:
        break;
    case OP_EDITOP_MERGE:
    case OP_EDITOP_REPLACE:
    case OP_EDITOP_CREATE:
	  /* deny an edit, if the test exist */
        if (testval) {
            res = ERR_NCX_ACCESS_DENIED;
        }
        break;
    case OP_EDITOP_DELETE:
	  /* deny an edit, if the test value set to “deny-delete” */
        if (testval &&
            !xml_strcmp(VAL_STR(testval), (const xmlChar *)"deny-delete") {

            res = ERR_NCX_ACCESS_DENIED;
        } else {
            res2 = NO_ERR;
        }
        break;
    default:
        res = SET_ERROR(ERR_INTERNAL_VAL);
    }

    if (res != NO_ERR) {
        agt_record_error(scb,
                         &msg->mhdr,
                         NCX_LAYER_CONTENT,
                         res,
                         NULL,
                         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
                         errorval,
                         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
                         errorval);
    }

    return res;

}  /* hooks_transhook_edit */

SIL-SA Transaction Hook Callback

A Transaction Hook is a function that is invoked within the transaction when an object is modified.

  • The Transaction Hook is similar to the Set Hook except this callback is invoked just before the data is committed to the running datastore.

  • This callback will be invoked after EDIT callbacks for the same object.

The Transaction Hook uses the same function template as the Set Hook. Refer to the SIL-SA Set Hook Callback section for details.

SIL-SA Transaction Hook Callback Initialization and Cleanup

A SIL-SA Transaction Hook callback function is registered with the 'agt_cb_sa_hook_register' function, described below.

The SIL-SA Transaction Hook uses the same registration function as the SIL-SA Set Hook. Refer to the SIL-SA Set Hook Callback Initialization and Cleanup section for details.

The Transaction Hook callbacks will not be invoked during explicit <validate> operation and the --sil-skip-load CLI parameter is set to TRUE.

Example Initialization function for a Transaction Hook callback:

static status_t interfaces_init (void)
{
    // ...
    /* Register an object specific Transaction Hook callback */
    res =
       agt_cb_sa_hook_register((const xmlChar *)"/if:trigger",
                               AGT_HOOKFMT_NODE,
                               AGT_HOOK_TYPE_TRANSACTION,
                               trans_sa_callback_edit);

    // ...
}

SIL-SA Transaction Hook Callback Function Example

The Transaction Hook can be set to specific object at run-time with a callback as follows. Register the Transaction Hook for the /example node of the example YANG module.

Whenever the /example node is edited, the Transaction Hook callback will be invoked and it will perform specific validation actions.

Example callback function:


/********************************************************************
* FUNCTION hook_cb
*
* Callback function for server object handler
* Used to provide a callback for a specific named object
*
* Transaction-Hook:
*   trigger: edit /example
*   effect:
*       - if testval node exist the DELETE operation will be denied
*       - if testval is false, the operation will be denied
*
* INPUTS:
*   scb == session control block making the request
*   msg == incoming rpc_msg_t in progress
*   editop == edit operation enumeration for the node being edited
*   newval == container object holding the proposed changes to
*           apply to the current config, depending on
*           the editop value. Will not be NULL.
*   curval == current container values from the <running>
*           or <candidate> configuration, if any. Could be NULL
*           for create and other operations.
*  transaction_id == transaction ID of the transaction control block in progress
*  isvalidate == TRUE if this Transaction is for <validate> operation
*  isload == TRUE if this Transaction is for a Load operation
*  isrunning == TRUE if this Transaction is for the the running datastore
*
* RETURNS:
*    status
********************************************************************/
static status_t
    hook_cb (ses_cb_t *scb,
             rpc_msg_t *msg,
             op_editop_t editop,
             val_value_t *newval,
             val_value_t *curval,
             const xmlChar *transaction_id,
             boolean isvalidate,
             boolean isload,
             boolean isrunning)
{
    status_t res = NO_ERR;
    status_t res2 = NO_ERR;
    val_value_t *errorval = (curval) ? curval : newval;

    const xmlChar *user = sil_sa_get_username();
    const xmlChar *client_addr = sil_sa_get_client_addr();

    if (LOGDEBUG2) {
        log_debug2("\n\n********************************************");
        log_debug2("\nEnter hooks_sethook_edit callback for silsa-test "
                    "---- 1");
        log_debug2("\ntransaction_id -- %s", transaction_id);
        log_debug2("\nuser_id -- %s", user);
        log_debug2("\nclient_addr -- %s", client_addr);
        log_debug2("\nisvalidate -- %s",
            isvalidate ? NCX_EL_TRUE : NCX_EL_FALSE);
        log_debug2("\nisload -- %s",
            isload ? NCX_EL_TRUE : NCX_EL_FALSE);
        log_debug2("\nisrunning -- %s",
            isrunning ? NCX_EL_TRUE : NCX_EL_FALSE);
        log_debug2("\n********************************************\n\n");
    }

    /* get the node that want to validate */
    val_value_t *testval =
        sil_sa_get_data(NCX_CFGID_RUNNING,
                        (const xmlChar *)"/if:interfaces/if:interface[if:name]/if:enabled",
                        &res2);

    switch (editop) {
    case OP_EDITOP_LOAD:
        break;
    case OP_EDITOP_MERGE:
    case OP_EDITOP_REPLACE:
    case OP_EDITOP_CREATE:
        if (testval) {
            res = ERR_NCX_ACCESS_DENIED;
        }
        break;
    case OP_EDITOP_DELETE:
        if (testval && VAL_BOOL(testval)) {
            res = ERR_NCX_ACCESS_DENIED;
        } else {
            res2 = NO_ERR;
        }
        break;
    default:
        res = SET_ERROR(ERR_INTERNAL_VAL);
    }

    if (res != NO_ERR) {
        agt_record_error(scb,
                         &msg->mhdr,
                         NCX_LAYER_CONTENT,
                         res,
                         NULL,
                         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
                         errorval,
                         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
                         errorval);
    }

    return res;

}  /* hook_cb */

Transaction Start

The Transaction Start function is the user/system callback that is invoked before any changes to the candidate database will be committed.

This is a good place to initialize specific pointers that will be cleaned after the transaction complete during the Transaction Complete callback invocation.

Transaction Start Callback

The following function template definition is used for Transaction Start callback functions:

typedef status_t (*agt_cb_trans_start_t)(agt_cfg_transaction_t *txcb)

Typedef of the trans_start callback.

The Start Transaction function is the user/system callback that is invoked before any changes to the candidate database will be committed.

Max Callbacks: No limit (except available heap memory)

Param txcb

transaction control block in progress

Return

status

Transaction Start Callback Initialization and Cleanup

The Transaction Start callback function is registered with the 'agt_cb_trans_start_register' function.

status_t agt_cb_trans_start_register(agt_cb_trans_start_t cbfn)

Register a Transaction Start callback.

This function registers a Transaction Start callback that will be called before any changes to the candidate database will be committed when the transaction is getting initialized.

Parameters

cbfn -- address of callback function to use

Returns

the status of the operation.

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

Example Usage:

 static status_t
      interfaces_init (void)
 {
     // ...

     /* Register a Transaction Start callback */
     res = agt_cb_trans_start_register(transaction_start);
     if (res != NO_ERR) {
         return res;
     }

     // ...
 }

The Transaction Start callback function is cleaned up with the 'agt_cb_trans_start_unregister' function.

void agt_cb_trans_start_unregister(agt_cb_trans_start_t cbfn)

Unregister a Transaction Start callback.

This function unregisters a Transaction Start callback.

Parameters

cbfn -- address of callback function to use

Example Usage:

void interfaces_cleanup (void)
{
    // ...

    /* Unregister a Transaction Start callback */
    agt_cb_trans_start_unregister(transaction_start);

    // ...
}

Transaction Start Callback Function Examples

The Transaction Start callback function can apply multiple actions before the transaction is actually started. This callback function may deny the start of the transaction if some specific check is unmet.

In this example, if the node “trigger” is present in the datastore then the server will deny the transaction.

/********************************************************************
* FUNCTION transaction_start
*
* Start Transaction callback
* The Start Transaction function is the user/system
* callback that is invoked before any changes to the
* candidate database will be committed.
*
* INPUTS:
*   txcb == transaction control block in progress
*
* RETURNS:
*   status
********************************************************************/
static status_t
    transaction_start (agt_cfg_transaction_t *txcb)
{

    log_debug("\nEnter transaction_start callback");
    status_t res = NO_ERR;

    /* deny the start of transaction if some specific check is unmet. E.g:
     * if there is a specific node present in the datastore
     */
    val_value_t *val =
       agt_val_get_data(txcb->cfg_id,
                        (const xmlChar *)"/if:trigger",
                        &res);
    if (val) {
        res = ERR_NCX_ACCESS_DENIED;
    }

    /* update a comment string of this transaction */
    txcb->comment = (const xmlChar *)"special transaction";

    /* callback could send custom notifications */

    /* callback could write a sys, audit, vendor specific,
     * etc log entries
     */


    return res;

}  /* transaction_start */

SIL-SA Transaction Start Callback

The SIL-SA Transaction Start function has the same purpose as the SIL Transaction Start function. The following function template definition is used for SIL-SA Transaction Start callback functions:

typedef status_t (*agt_cb_sa_trans_start_t)(const xmlChar *transaction_id, boolean isvalidate, boolean isrollback, boolean isrunning)

Typedef of the sa_trans_start callback.

The Start Transaction function is the user/system callback that is invoked before any changes to the candidate database will be committed. This is for SIL-SA subsystem only

Max Callbacks: No limit (except available heap memory)

Param txcb

transaction ID in progress

Param isvalidate

TRUE if this is Transaction is for Validate

Param isrollback

TRUE if this is Transaction for Rollback or Load

Param isrunning

TRUE if running datastore is being modified

Return

status

SIL-SA Transaction Start Callback Initialization and Cleanup

The SIL-SA Transaction Start callback function is registered with the 'agt_cb_sa_trans_start_register' function.

status_t agt_cb_sa_trans_start_register(agt_cb_sa_trans_start_t cbfn)

Register a Transaction Start callback.

This function registers a Transaction Start callback that will be called before any changes to the candidate database will be committed when the transaction is getting initialized. This API is available only for SIL-SA Subsystem

Parameters

cbfn -- address of callback function to use

Returns

the status of the operation.

For SIL-SA the registration is done during the Initialization Phase 1 before the startup configuration is loaded into the running configuration database.

Example Usage:

static status_t interfaces_init (void)
{
    // ...

    /* Register a Transaction Start callback. */
    res = agt_cb_sa_trans_start_register(transaction_start);
    if (res != NO_ERR) {
        return res;
    }

    // ...
}

The callbacks can be cleaned up automatically whether when the subsystem goes away or when the server shuts down, but there will not be any cleanup during <unload> operation. As a result, the SIL-SA code must provide an unregister function to cleanup the callback during <unload> operation.

The Transaction Start callback function is cleaned up with the 'agt_cb_trans_start_unregister' function.

void agt_cb_sa_trans_start_unregister(agt_cb_sa_trans_start_t cbfn)

Uregister a Transaction Start callback.

This function unregisters a Transaction Start callback. This API is available only for SIL-SA Subsystem

Parameters

cbfn -- address of callback function to unregister

SIL-SA Transaction Start Callback Function Examples

This example callback simply logs all the available parameters and denies the transaction if it is for the <validate> operation on the running datastore, and return “Invalid Number” error status.

/********************************************************************
* FUNCTION silsa_transaction_start
*
* Start Transaction SA callback
* The Start Transaction function is the user/system
* callback that is invoked before any changes to the
* database will be committed.
*
* RETURNS:
*   status
********************************************************************/
static status_t
    silsa_transaction_start (const xmlChar *transaction_id_val,
                             boolean isvalidate_val,
                             boolean isrollback_val,
                             boolean isrunning_val)
{
    if (!transaction_id_val) {
        log_error("\ntransaction_id value not set");
        return ERR_INTERNAL_VAL;
    }

    const xmlChar *user = sil_sa_get_username();
    const xmlChar *client_addr = sil_sa_get_client_addr();

    if (LOGDEBUG2) {
        log_debug2("\n\n********************************************"
                   "\nEnter silsa_transaction_start callback for silsa-test "
                   "---- 2"
                   "\ntransaction_id -- %s"
                   "\nuser_id -- %s"
                   "\nclient_addr -- %s"
                   "\nisvalidate -- %s"
                   "\nisrollback -- %s"
                   "\nisrunning -- %s"
                   "\n********************************************\n\n",
                   transaction_id_val,
                   user,
                   client_addr,
                   isvalidate_val ? NCX_EL_TRUE : NCX_EL_FALSE,
                   isrollback_val ? NCX_EL_TRUE : NCX_EL_FALSE,
                   isrunning_val ? NCX_EL_TRUE : NCX_EL_FALSE);
    }

    /* return an error when "validate" operation is in progress */
    if (!isrollback_val && isvalidate_val && isrunning_val) {
        return ERR_NCX_INVALID_NUM;
    } else {
        return NO_ERR;
    }

}  /* silsa_transaction_start */

In the example, two additional SIL-SA APIs are used to retrieve current transaction “user name” and “address”:

/* Get the user_id value from the message header */
const xmlChar *user = sil_sa_get_username();

/* Get the client address (client_addr value from the message header) */
const xmlChar *client_addr = sil_sa_get_client_addr();

Transaction Complete

The Transaction Complete function is the user/system callback that is invoked after the transactions has been processed.

Transaction Complete Callback

The following function template definition is used for Transaction Complete callback functions:

typedef void (*agt_cb_trans_complete_t)(agt_cfg_transaction_t *txcb)

Typedef of the trans_complete callback.

The Transaction Complete function is the user/system callback that is invoked after the transactions has been processed.

Max Callbacks: No limit (except available heap memory) 1 Per SIL

Param txcb

transaction control block in progress

Transaction Complete Callback Initialization and Cleanup

The Transaction Complete callback function is registered with the 'agt_cb_trans_complete_register' function.

status_t agt_cb_trans_complete_register(agt_cb_trans_complete_t cbfn)

Register a Transaction Complete callback.

This function registers a Transaction Complete callback that will be called after the transactions has been processed.

Parameters

cbfn -- address of callback function to use

Returns

the status of the operation

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

Example Usage:

static status_t
     interfaces_init (void)
{
    // ...

    /* Register a Transaction Complete callback. */
    res = agt_cb_trans_complete_register(transaction_complete);
    if (res != NO_ERR) {
        return res;
    }

    // ...
}

The Transaction Complete callback function clean up is done with the 'agt_cb_trans_complete_unregister' function.

void agt_cb_trans_complete_unregister(agt_cb_trans_complete_t cbfn)

Unregister a Transaction Complete callback.

This function unregisters a Transaction Complete callback.

Parameters

cbfn -- address of callback function to unregister

Transaction Complete Callback Function Example

In this example, the Transaction Complete callback function simply logs a message. It is usually used to cleanup any system resources that were allocated during the transaction.

/********************************************************************
* FUNCTION transaction_complete
*
* Complete Transaction callback
* The Transaction Complete function is the
* user/system callback that is invoked after
* the transactions has been processed.
*
* INPUTS:
*   txcb == transaction control block in progress
********************************************************************/
static void
    transaction_complete (agt_cfg_transaction_t *txcb)
{

    log_debug("\nEnter transaction_complete callback");

    /* send custom notifications */

    /* cleanup any system resources for this transaction */

}  /* transaction_complete */

SIL-SA Transaction Complete Callback

The SIL-SA Transaction Complete function is the similar to the SIL Transaction Complete function.

The following function template definition is used for SIL-SA Transaction Complete callback functions:

typedef void (*agt_cb_sa_trans_complete_t)(const xmlChar *transaction_id_val)

Typedef of the sa_trans_complete callback.

The Complete Transaction function is the user/system callback that is invoked at the end of the transaction This is for SIL-SA subsystem only

Max Callbacks: No limit (except available heap memory)

Param transaction_id_val

transaction ID in progress

SIL-SA Transaction Complete Callback Initialization and Cleanup

The SIL-SA Transaction Complete callback function is registered with the 'agt_cb_sa_trans_complete_register' function.

status_t agt_cb_sa_trans_complete_register(agt_cb_sa_trans_complete_t cbfn)

Register a Transaction Complete callback.

This function registers a Transaction Complete callback that will be called after the transactions has been processed. This API is available only for SIL-SA Subsystem

Parameters

cbfn -- address of callback function to use

Returns

the status of the operation.

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

Example Usage:

static status_t interfaces_init (void)
{
    // ...

    /* Register a Transaction Complete callback. */
    res = agt_cb_sa_trans_complete_register(silsa_transaction_complete);
    if (res != NO_ERR) {
        return res;
    }

    // ...
}

The SIL-SA code must provide an unregister function to cleanup the callback during <unload> operation.

void agt_cb_sa_trans_complete_unregister(agt_cb_sa_trans_complete_t cbfn)

Uregister a Transaction Complete callback.

Uregister a Transaction Complete callback. This API is available only for SIL-SA Subsystem

Parameters

cbfn -- address of callback function to unregister

SIL-SA Transaction Complete Callback Function Example

In this example, the Transaction Complete callback function simply logs an error message.

/********************************************************************
* FUNCTION silsa_transaction_complete
*
* Complete Transaction callback
* The Complete Transaction function is the user/system
* callback that is invoked at the end of the transaction
*
*
* Max Callbacks: Unlimited
*
* INPUTS:
*   transaction_id_val == transaction id
*
* RETURNS:
*   none
********************************************************************/
static void
    silsa_transaction_complete (const xmlChar *transaction_id_val)
{

    if (!transaction_id_val) {
        log_error("\ntransaction_id value not set");
        return;
    }

    /* This might run some cleanup of values that were set
     * during the Transaction Start callback
     */

}  /* silsa_transaction_complete */

Set Order Hook

The Set Order Hook callback function is the user/system callback that is used to provide an access for a specific list instance object and modify its secondary SIL priority.

  • It is invoked in document order for each edited instance of the specified object.

  • The callback returns the desired secondary SIL priority for the specific list instance.

  • This callback is invoked once per edited instance and after any Set Hook is called for the object and instance.

  • The Set Order Hook callback function can be registered only for YANG “list” objects.

  • The Set Order Hook callback can be used with the Set Hook or Post Set Hook callback or by itself.

  • The Set Order Hook is invoked before SIL or any other callbacks for the same object.

Set Order Hook Callback

The following function template definition is used for Set Order Hook callback functions:

typedef uint8 (*agt_cb_order_hook_t)(agt_cfg_transaction_t *txcb, op_editop_t editop, val_value_t *newval, val_value_t *curval, status_t *res)

Typedef of the agt_order_hook_cb callback.

Callback function for server object handler Used to provide a callback for a specific named object

Set-Order-Hook: Invoked in document order for each edited instance of the specified object. The callback needs to return the desired secondary SIL priority for the specific list instance

This callback is invoked once per edited instance and after any Set Hook is called for the object and instance.

Param txcb

transaction control block in progress

Param editop

edit operation enumeration for the node being edited

Param newval

container object holding the proposed changes to apply to the current config, depending on the editop value. Will not be NULL.

Param curval

current container values from the <running> or <candidate> configuration, if any. Could be NULL for create and other operations.

Param res

address of return status

Retval *res

status of callback; status error will cause the transaction to be terminated and rollback started

Return

the secondary SIL priority to assign to the object instance

Set Order Hook Callback Initialization and Cleanup

The Set Order Hook callback function is registered with the 'agt_cb_order_hook_register' function.

status_t agt_cb_order_hook_register(const xmlChar *defpath, agt_cb_order_hook_t cbfn)

Register an object specific Set-Order-Hook callback function.

Needs to be registered for the list object that will be 2nd-ordered Will not be called for subtree list nodes

Parameters
  • defpath -- Xpath with default (or no) prefixes defining the object that will get the callback

  • cbfn -- address of callback function to use for all callback phases

Returns

the status of the operation

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

Example Usage:

static status_t interfaces_init (void)
{
    // ...

    /* Register an object specific Set-Order-Hook callback function */
    res = agt_cb_order_hook_register((const xmlChar*)"/if:interface/if:interface",
                                     set_order_hook);
    if (res != NO_ERR) {
        return res;
    }

    // ...
}

Whenever the /if:interface/if:interface list is getting modified, the callback function will be invoked and desired secondary SIL priority for the list instance can be specified. As a result, the server will apply list of edits based on this priority.

The callbacks cleanup is done during the module Cleanup Phase.

void agt_cb_order_hook_unregister(const xmlChar *defpath)

Unregister a Set-Order-Hook callback.

This function unregisters a Set-Order-Hook callback.

Parameters

defpath -- Xpath with default (or no) prefixes defining the object that will get the callback

Example Usage:

void interfaces_cleanup (void)
{
    // ...

    /* Unregister an object specific Set-Order-Hook callback function */
    agt_cb_order_hook_unregister((const xmlChar*)"/if:interface/if:interface");

    // ...
}

Set Order Hook Callback Function Example

In this example, the Set Order Hook callback function applies SIL priority to a list edit based on a key value.

  • Whenever a /if:interfaces/if:interface list entry is edited, the callback is invoked and additionally assigns desired priority to this edit.

  • The priority will be assigned only if there is a new value.

  • It will be assigned only if the edit operation is not “delete” or “remove” because during the edits for those operations the server does not allocate a new value.


/********************************************************************
* FUNCTION set_order_hook
*
* Callback function is
* used to provide a callback for a specific named object
*
* This callback is invoked once per instance before any
* Set Hook or Post Set Hook is called for the object and instance.
*
* INPUTS:
*   txcb == transaction control block in progress
*   editop == edit operation enumeration for the node being edited
*   newval == container object holding the proposed changes to
*           apply to the current config, depending on
*           the editop value. Will not be NULL.
*   curval == current container values from the <running>
*           or <candidate> configuration, if any. Could be NULL
*           for create and other operations.
*   res == address of return status
* OUTPUTS:
*    *res == status of callback; status == error will cause the
*            transaction to be terminated and rollback started
* RETURNS:
*    the secondary SIL priority to assign to the object instance
********************************************************************/
static uint8
    set_order_hook (agt_cfg_transaction_t *txcb,
                    op_editop_t editop,
                    val_value_t  *newval,
                    val_value_t  *curval,
                    status_t *res)
{
    (void)txcb;
    (void)editop;
    (void)curval;

    uint8 retprior = 0;

    /* Set the priority only if the operation is not delete and if
     * a new value has keys, that will be compared later
     */
    if (newval && val_has_index(newval)) {

        /* Get the first index entry, if any for this value node */
       const val_index_t *c1 = val_get_first_index(newval);
       if (!c1) {
		return ERR_INTERNAL_VAL;
        }

        if (!xml_strcmp(VAL_STR(c1->val), (const xmlChar *)"vlan1")) {
            log_debug("\n Setting Priority to 140 for index:%s \n",
                VAL_STR(c1->val));

            retprior = 100;
        } else if (!xml_strcmp(VAL_STR(c1->val), (const xmlChar *)"ethernet1/1/10")) {
            log_debug("\n Setting Priority to 160 for index:%s \n",
                VAL_STR(c1->val));

            retprior = 150;
        } else {
            log_debug("\n Setting Priority to 130 for index:%s \n",
                VAL_STR(c1->val));

            retprior = 200;
        }
    }

    return retprior;

}  /* set_order_hook */

For example, if the north bound agent is creating 3 interfaces with different key values at the same time using the following <edit-config> RPC:

 <edit-config>
   <target>
     <candidate/>
   </target>
   <default-operation>merge</default-operation>
   <test-option>set</test-option>
   <config>
     <interfaces>
       <interface>
         <name>ethernet1/1/1</name>
       </interface>
       <interface>
         <name>vlan1</name>
       </interface>
       <interface>
         <name>ethernet1/1/10</name>
       </interface>
     </interfaces>
   </config>
</edit-config>

By the regular server logic, the server would validate/apply/commit these interfaces in order they are specified in the edit. However, using the Set Order Hook the server now will apply these “interfaces” based on their priority assigned in the callback function. The first list instance that will be processed by the server will be the “interface” with the key value set to “vlan1” since it has the highest priority - “100”.

So, the edits order would look as follows, from the server's logging:

***** start commit phase on candidate for session 5, transaction 1234358 *****
Start full commit of transaction 1234358: 3 edits on candidate config
edit-transaction 1234358: on session 5 by --@::1
  message-id: --
  trace-id: --
  datastore: candidate
  operation: create
  target: /if:interfaces/if:interface[if:name="vlan1"]
  comment: none

edit-transaction 1234358: on session 5 by --@::1
  message-id: --
  trace-id: --
  datastore: candidate
  operation: create
  target: /if:interfaces/if:interface[if:name="ethernet1/1/10"]
  comment: none

edit-transaction 1234358: on session 5 by --@::1
  message-id: --
  trace-id: --
  datastore: candidate
  operation: create
  target: /if:interfaces/if:interface[if:name="ethernet1/1/1"]
  comment: none

Add Edit

The 'Add Edit' API is used to add an edit to the transaction in progress.

Note

Manipulation of the datastores is only allowed for Set Hook or Post Set Hook callbacks. If Transaction Hook or Start/Complete Transaction callbacks call the Add Edit API the operation will be ignored.

Add Edit API

The following function template definition is used for Add Edit API callback functions:

status_t agt_val_add_edit(ses_cb_t *scb, rpc_msg_t *msg, agt_cfg_transaction_t *txcb, const xmlChar *defpath, val_value_t *edit_value, op_editop_t editop)

Create new edit based on edit_value.

if its NULL or invalid the error will be generated.

Only allowed for Set Hooks or Post Set Hook, the rest are ignored. Add_edit is not allowed for default nodes or default NP-contaners

Parameters
  • scb -- session invoking the edit

  • msg -- incoming commit rpc_msg_t in progress

  • txcb -- transaction in progress

  • defpath -- XPath path of object instance

  • edit_value -- val_value_t representing newnode in transaction only needed for create, merge, replace, not delete

  • editop -- edit operation to use

Returns

status

The following example from the hooks_sethook_edit function shows how to create and add an edit.

 /* find object template of the desired node */
 obj_template_t *targobj =
           ncx_match_any_object_ex((const xmlChar *)"ietf-interfaces",
                                   (const xmlChar *)"interfaces",
                                   FALSE,
                                   NCX_MATCH_FIRST,
                                   FALSE,
                                   &res);
 if (!targobj) {
     return ERR_NCX_INVALID_VALUE;
 }

/* create edit_value container value */
 val_value_t *editval = val_new_value();
 if (editval == NULL) {
     return ERR_INTERNAL_MEM;
 }
 val_init_from_template(editval, targobj);

 /* malloced and construct list value, for more
  * examples refer to libhooks-test/src/hooks-test.c library
  */
 uint32 key = 11;
 val_value_t *list_value = create_list_entry(VAL_OBJ(editval),
                                             key,
                                             &res);
 if (!list_value) {
     val_free_value(editval);
     return res;
 }

 /* add a new list entry */
 val_add_child_sorted(list_value, editval);

 /* add a new edit, MERGE on defpath with 1 new list entry */
 if (res == NO_ERR) {
     res = agt_val_add_edit(scb,
                            msg,
                            txcb,
                            defpath,
                            editval,
                            OP_EDITOP_MERGE);
 }

 /* clean up the editval */
 val_free_value(editval);

Add Edit Extended API

The Add Edit Extended API is used to insert or move new list or leaf-list entries to the transaction in progress.

The following function template definition is used for Add Edit Extended API callback functions:

status_t agt_val_add_edit_ex(ses_cb_t *scb, rpc_msg_t *msg, agt_cfg_transaction_t *txcb, const xmlChar *defpath, val_value_t *edit_value, const xmlChar *edit_operation, const xmlChar *insert_where, const xmlChar *insert_point)

Create a new edit based on edit_value.

if its NULL or invalid the error will be generated. (extended)

Move or insertion OP available.

Only allowed for Set Hooks or Post Set Hook, the rest are ignored. Add_edit is not allowed for default nodes or default NP-contaners

Parameters
  • scb -- session invoking the edit

  • msg -- incoming commit rpc_msg_t in progress

  • txcb -- transaction in progress

  • defpath -- XPath path of object instance

  • edit_value -- val_value_t representing newnode in transaction only needed for create, merge, replace, not delete

  • edit_operation -- <operation string>="">.

    • "create"

    • "delete"

    • "insert"

    • "merge"

    • "move"

    • "replace"

    • "remove"

  • insert_where -- <insert enum="" string>="">.

    • "before"

    • "after"

    • "first"

    • "last"

    • Will be used only if the operations are "move" or "insert". Ignored otherwise.

  • insert_point --

    is a XPath encoded string like the defpath. Only for 'before' or 'after' insert_where paramter. The insert_where must be set to 'before' or 'after' if insert_point specified. Will be used only if the operations are "move" or "insert".

    Ignored otherwise.

    E.g: "/test3[string.1='entry2'][uint32.1='2']"

Returns

status

Note

The server does not support the 'insert' and 'move' operation on the nodes that are being modified at the same transaction at the same time.

Usage Example:

Refer to the Set Hook example containing 'hooks_sethook_edit_insert' in the Set Hook Callback and Add Edit Extended API Function Examples section.

The code snippet below from that function adds the edit using this API:

/* add a new edit on defpath and populate new entry */
if (res == NO_ERR) {
    res = agt_val_add_edit_ex(scb,
                              msg,
                              txcb,
                              defpath,
                              editval,
                              edit_operation,
                              insert_where,
                              insert_point);
}

if (editval) {
    /* clean up the editval */
    val_free_value(editval);
}

Add Edit Maximum API

The Add Edit maximum API is used to insert or move new list or leaf-list entries to the transaction in progress and also controls whether the server should invoke callbacks for added edits or not. Manipulation with datastore are only allowed for Set Hook or Post Set Hook callbacks.

The following function template definition is used for Add Edit Maximum API callback functions:

status_t agt_val_add_edit_max(ses_cb_t *scb, rpc_msg_t *msg, agt_cfg_transaction_t *txcb, const xmlChar *defpath, val_value_t *edit_value, const xmlChar *edit_operation, const xmlChar *insert_where, const xmlChar *insert_point, boolean skip_cb)

Create a new edit based on edit_value.

if its NULL or invalid the error will be generated.

Move or insertion OP available. Skip callbacks for added edits option is available.

Only allowed for Set Hooks or Post Set Hook, the rest are ignored.

Parameters
  • scb -- session invoking the edit

  • msg -- incoming commit rpc_msg_t in progress

  • txcb -- transaction in progress

  • defpath -- XPath path of object instance

  • edit_value -- val_value_t

    representing newnode in transaction

    only needed for create, merge, replace, not delete

  • edit_operation -- <operation string>="">.

    • "create"

    • "delete"

    • "insert"

    • "merge"

    • "move"

    • "replace"

    • "remove"

  • insert_where -- <insert enum="" string>="">.

    • "before"

    • "after"

    • "first"

    • "last"

    • Will be used only if the operations are "move" or "insert". Ignored otherwise.

  • insert_point --

    is a XPath encoded string like the defpath. Only for 'before' or 'after' insert_where paramter. The insert_where must be set to 'before' or 'after' if insert_point specified. Will be used only if the operations are "move" or "insert".

    Ignored otherwise.

    E.g: "/test3[string.1='entry2'][uint32.1='2']"

  • skip_cb -- TRUE if DO NOT invoke callbacks for an edded edit if any. FALSE if SKIP any callback for added edit including Transaction, EDIT1, EDIT2 callbacks Only when target=running

Returns

status

The following code snippet from agt/agt_val_silcall.c shows the usage of this function:

agt_sil_added_edit_t *edit =
    (agt_sil_added_edit_t *)dlq_firstEntry(edded_editQ);
for (; edit && (res == NO_ERR);
       edit = (agt_sil_added_edit_t *)dlq_nextEntry(edit)) {

    res =
        agt_val_add_edit_max(scb,
                             msg,
                             txcb,
                             edit->path,
                             edit->editval,
                             edit->editop,
                             edit->where,
                             edit->point,
                             edit->skipcb);
    if (res != NO_ERR) {
        log_error("\nError: failed to add edit from subsystem '%s' "
                  "defpath '%s' (%s)\n",
                  subsys_id,
                  edit->path,
                  get_error_string(res));
    }
}

SIL-SA Add Edit API

The SIL-SA version of the Add Edit API has a different function template since the transaction control block is not available in the SIL-SA code. The complete functionality of the Add Edit API is still available and all the edits will be added the same way as in SIL code.

The following function template definition is used for SIL-SA Add Edit API callback functions:

status_t sil_sa_add_edit(const xmlChar *defpath, val_value_t *edit_value, const xmlChar *edit_operation, const xmlChar *insert_where, const xmlChar *insert_point, boolean skip_cb)

Add an edit to the current transaction from SIL-SA.

Save an added edit in the SIL-SA Control block for further hook-response processing

Move or insertion OP available. Skip callbacks for added edits option is available.

Only allowed for Set Hooks or Post Set Hook, the rest are ignored.

See also

db_api_edit_full2

Parameters
  • defpath -- XPath path of object instance

  • edit_value -- val_value_t

    representing newnode in transaction

    only needed for create, merge, replace, not delete

  • edit_operation -- <operation string>="">

    • "create"

    • "delete"

    • "insert"

    • "merge"

    • "move"

    • "replace"

    • "remove"

  • insert_where -- <insert enum="" string>=""> (NULL if not used)

    • "before"

    • "after"

    • "first"

    • "last"

    • Will be used only if the operations are "move" or "insert".

      Ignored otherwise.

  • insert_point --

    is a XPath encoded string like the defpath. Only for 'before' or 'after' insert_where paramter. The insert_where must be set to 'before' or 'after' if insert_point specified. Will be used only if the operations are "move" or "insert".

    Ignored otherwise.

    E.g: "/test3[string.1='entry2'][uint32.1='2']"

  • skip_cb -- TRUE if DO NOT invoke callbacks for an edded edit if any. FALSE if SKIP any callback for added edit including Transaction, EDIT1, EDIT2 callbacks Only when target=running

Returns

status

The following code snippet shows how this API may be used,

if (newval &&
    !xml_strcmp(VAL_NAME(newval), (const xmlChar *)"ports")) {

    const xmlChar *edit_operation = (const xmlChar *)"delete";
    const xmlChar *insert_point = NULL;
    const xmlChar *insert_where = NULL;
    boolean skip_cb = FALSE;

    val_value_t *testval = sil_sa_get_data(NCX_CFGID_RUNNING,
                                           defpath,
                                           &res2);
    if (testval) {
        log_info("\n+++ Got testval %s (%s)",
                 VAL_NAME(testval),
                 get_error_string(res));
    }

    val_value_t *testval2 = sil_sa_get_data(NCX_CFGID_RUNNING,
                                            defpath2,
                                            &res2);
    if (testval2) {
        log_info("\n+++ Got testval2 %s (%s)",
                 VAL_NAME(testval2),
                 get_error_string(res));
    }

    if (testval) {
        res =
            sil_sa_add_edit(defpath,
                            NULL,               // edit_value
                            edit_operation,
                            insert_where,
                            insert_point,
                            skip_cb);
    }
}

Get Data

The 'Get Data' API is used to retrieve data nodes from the server.

If the XPath expression matches multiple nodes, then only the first instance is returned. The exact instance is implementation-dependent if the data is “ordered-by system”.

Get Data API

The following function template definition is used for Get Data API callback functions:

val_value_t *agt_val_get_data(ncx_cfg_t cfg_id, const xmlChar *defpath, status_t *retres)

Invoke get data callback.

Parameters
  • cfg_id -- configuration datastore ID to use

  • defpath -- XPath path of object instance

  • retres -- address of return status

Return values

*retres -- return status

Returns

pointer to data node if found

  • This should be treated as a const pointer

  • Returned pointer is the real data from the target datastore

The following code snippet shows an example of this API:

/* defpath specified target */
const xmlChar *defpath =
    (const xmlChar *)"/hk:interfaces/hk:interface[name='ypw1027-vlan1']/hk:invocation-check";
status_t res = NO_ERR;
val_value_t *useval =
    agt_val_get_data(NCX_CFGID_CANDIDATE, defpath, &res);
if (useval) {
    // use the value from server here

}

// DO NOT FREE useval! Treat as const pointer instead!

SIL-SA Get Data API

The SIL-SA version of the Get Data API has a different template definition than the SIL version. The following function template definition is used for SIL-SA Get Data API callback functions:

val_value_t *sil_sa_get_data(ncx_cfg_t cfg_id, const xmlChar *defpath, status_t *retres)

Get some YANG data from the server.

Get the val_value based on Xpath of object instance This function will return value only if there is existing node in the datastore or there is defaults for the node.

Parameters
  • cfg_id -- configuration datastore ID to use

  • defpath -- XPath path of object instance

  • retres -- address of return status

Return values

*retres -- return status

Returns

pointer to requested data if found.

  • Must NOT free this value!

  • value stored as sil_sa_cb.hook_get_value

  • Only 1 GET from SIL-SA at a time can be done

The following code snippet shows an example of this API:

status_t res = NO_ERR;
const xmlChar *defpath =
    (const xmlChar *)"/testsystem";
val_value_t *testval =
    sil_sa_get_data(NCX_CFGID_CANDIDATE, defpath, &res);
if (testval) {
    log_debug("\nGot testval %s (%s)",
             VAL_NAME(testval),
             get_error_string(res));

    // use the testval
}

// DO NOT FREE testval! Treat as const pointer instead!

Startup Hook

The Startup Hook function is a user/system callback that is invoked before any changes are done to the <startup> datastore.

  • It is invoked only if the :startup capability is enabled.

  • The Startup Hook callback is invoked just before the <startup> datastore is modified.

  • Any number of callbacks can be registered.

  • This callback can reject the operation by returning an error.

  • If the callback fails then no further actions will be executed and the server would reply with the error.

The Startup Hook callback is invoked before any modifications to the <startup> datastore during any edit operation, such as <edit-config>, <copy-config> and <delete-config> operations.

If an application needs to perform additional validations, check or any other actions before the <startup> datastore is modified or accessed, the Startup Hook can be used.

The callback will be called for the following operations:

Startup Hook Callback

The following function template definition is used for Startup Hook callback functions:

typedef status_t (*agt_cb_startup_hook_t)(ses_cb_t *scb, rpc_msg_t *msg, cfg_template_t *source_config, cfg_template_t *target_config)

Typedef of the agt_cb_startup_hook_t callback.

The Startup Hook callback is the user/system callback that is invoked right before the <startup> is getting modified.

The Startup Hook is object independent and module independent which means you don't have to link them to the specific object as it's done for EDIT or GET callbacks, and you don't have to link them to any specific module

Param scb

session control block making the request

Param msg

incoming rpc_msg_t in progress

Param source_config

datastore which is being copied

Param target_config

datastore that is being edited

Return

status

Startup Hook Callback Initialization and Cleanup

The Startup Hook callback function is registered with the 'agt_cb_startup_hook_register' function. The registration is done during the Initialization Phase 1.

status_t agt_cb_startup_hook_register(agt_cb_startup_hook_t cbfn)

Register a Startup Hook callback.

This function registers a Startup Hook callback that will be called before any changes to the startup file/database.

Parameters

cbfn -- address of callback function to use

Returns

the status of the operation.

Example Usage:

static status_t interfaces_init (void)
{
    // ...

    /* register startup hook callback */
    res = agt_cb_startup_hook_register(startup_hook_callback);
    if (res != NO_ERR) {
        return res;
    }

    // ...
}

Now whenever the <startup> datastore is being modified the 'startup_hook_callback' function is called.

void agt_cb_startup_hook_unregister(agt_cb_startup_hook_t cbfn)

Unregister a Startup Hook callback.

This function unregisters a Startup Hook callback.

Parameters

cbfn -- address of callback function to use

Returns

the status of the operation.

Example Usage:

void interfaces_cleanup (void)
{
    // ...

    /* Unregister startup hook callback */
    agt_cb_startup_hook_unregister(startup_hook_callback);

    // ...
}

Startup Hook Callback Example

This example callback function simply logs a message. It could reject the operation after checking the parameters.

/********************************************************************
* FUNCTION startup_hook_callback
*
* Startup Hook callback
* The Startup Hook Complete function is the
* user/system callback that is invoked before
* any changes to the <startup> during edit-config, copy-config and
* delete-config operation.
*
* Max Callbacks: No limit (except available heap memory)
*
* INPUT:
* scb == session control block making the request
* msg == incoming rpc_msg_t in progress
* source_config == datastore which is being copied
* target_config == datastore that is being edited
*
* RETURNS:
* status
********************************************************************/
static status_t startup_hook_callback (ses_cb_t *scb,
                                       rpc_msg_t *msg,
                                       cfg_template_t *source_config,
                                       cfg_template_t *target_config)
{
    (void)scb;
    (void)msg;
    (void)source_config;
    (void)target_config;

    log_debug("\n\nEnter startup_hook_callback callback");

    /* notify application that the <startup> is being modified */
    log_debug("\n <startup> is being modified");

    return NO_ERR;

} /* startup_hook_callback */

Using Commit Completeness Callbacks

There are 4 Commit Completeness callbacks:

  • Validate Complete

  • Apply Complete

  • Commit Complete

  • Rollback Complete

The Commit Completeness Callbacks are not regular SIL callbacks. Unlike an EDIT1 or EDIT2 callback, these callbacks are not specific to any particular YANG data node.

All edit validation for the phase has been completed already when a Commit Completeness Callback is invoked.

Note

These callbacks are invoked after the entire transaction phase is done, so edits can be checked without worrying about the order individual edits are applied.

  • Use these callbacks instead of using SIL Priority!

  • None of the Commit Completeness callbacks are allowed to change the edit transaction in progress

  • The callbacks are expected to inspect and validate the edit transaction, and then approve or reject the transaction.

Validate Complete

The Validate Complete function is the user/system callback that is invoked after the Validate Phase has been processed during the <commit> operation.

  • If the callback fails the status of the failing callback is returned immediately and no further callbacks are made. As a result, the server will abort the commit.

  • The Validate Complete callback is only called after the commit operation for the Validate phase has finished not after the commit for the specific module or edit is done.

  • If there are multiple callbacks registered, all the registered callbacks will be called one after another right after the Validate Phase during the commit operation is done.

Validate Complete Callback

The following function template definition is used for Validate Complete callback functions:

typedef status_t (*agt_cb_validate_complete_t)(ses_cb_t *scb, rpc_msg_t *msg, val_value_t *candidate, val_value_t *running)

Typedef of the agt_cb_validate_complete_t callback.

The Validate Complete callback is the user/system callback that is invoked after the Validate Phase has been processed during the <commit> operation.

The Validate Complete is object independent and module independent which means you don't have to link them to the specific object as it's done for EDIT or GET callbacks, and you don't have to link them to any specific module

Max Callbacks: No limit (except available heap memory)

Param scb

session control block making the request

Param msg

incoming rpc_msg_t in progress

Param candidate

candidate val_value_t for the config database to use

Param running

running val_value_t for the config database to use

Return

status

Validate Complete Callback Initialization and Cleanup

The Validate Complete callback function is registered with the 'agt_cb_validate_complete_register' function, The registration is done during the Initialization Phase 1 before the startup configuration is loaded into the running configuration database.

status_t agt_cb_validate_complete_register(agt_cb_validate_complete_t cbfn)

Register a Validate Complete callback.

This function registers a Validate Complete callback that will be called after the Validation phase has been processed during the <commit>.

Parameters

cbfn -- address of callback function to use

Returns

the status of the operation.

Example Usage:

static status_t interfaces_init (void)
{
    // ...

    /* register validate complete callback */
    res =
        agt_cb_validate_complete_register(validate_compl_callback);
    if (res != NO_ERR) {
        return res;
    }

    // ...
}

Now, whenever the Validate Phase is done for the <commit> operation, the callback function will be called and provide the access to the candidate and running datastores.

void agt_cb_validate_complete_unregister(agt_cb_validate_complete_t cbfn)

Unregister a Validate Complete callback.

This function unregisters a Validate Complete callback.

Parameters

cbfn -- address of callback function to use

Example Usage:

void interfaces_cleanup (void)
{
    // ...
    /* Unregister Validate Complete callback */
    agt_cb_validate_complete_unregister(validate_compl_callback);

    // ...
}

Validate Complete Callback Example

The following example code shows a Validate Complete callback. It does not do anything except return NO_ERR.

/********************************************************************
* FUNCTION   validate_compl_callback
*
* Validate Complete callback
*
* Max Callbacks: No limit (except available heap memory)
*
* INPUTS:
*   scb == session control block making the request
*   msg == incoming rpc_msg_t in progress
*   candidate == candidate val_value_t for the config database to use
*   running == running val_value_t for the config database to use
*
* RETURNS:
*   status
********************************************************************/
static status_t
    validate_compl_callback (ses_cb_t *scb,
                               rpc_msg_t *msg,
                               val_value_t *candidate,
                               val_value_t *running)
{
    (void)scb;
    (void)msg;
    (void)candidate;
    (void)running;

    /* notify application that the Validate phase is done */

    /* validate candidate */

    /* validate running */

    return NO_ERR;

}  /* validate_compl_callback */

SIL-SA Validate Complete

The SIL-SA version of the Validate Complete callback function is similar to the SIL Validate Complete function.

For SIL-SA usage the server does not provide running and candidate values. Instead it passes the transaction ID for reference and Get Data API can be used to run additional validation if required.

SIL-SA Validate Complete Callback

The following function template definition is used for SIL-SA Validate Complete callback functions:

typedef status_t (*agt_cb_sa_validate_complete_t)(const xmlChar *transaction_id)

Typedef of the agt_cb_sa_validate_complete_t callback.

The SIL-SA Validate Complete callback is the user/system callback that is invoked after the Validate Phase has been processed during the <commit> operation.

SIL-SA Version

Max Callbacks: No limit (except available heap memory)

Param transaction_id

transaction ID in progress

Return

status

SIL-SA Validate Complete Callback Initialization and Cleanup

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

status_t agt_cb_sa_validate_complete_register(agt_cb_sa_validate_complete_t cbfn)

Register a SIL-SA Validate Complete callback.

This function registers a SIL-SA Validate Complete callback that will be called after the Validation phase has been processed during the <commit>.

Parameters

cbfn -- address of SIL-SA callback function to use

Returns

the status of the operation

Example Usage:

static status_t interfaces_init (void)
{
    // ...

    /* register SIL-SA validate complete callback */
    res = agt_cb_sa_validate_complete_register(hooks_validate_complete_cb);
    if (res != NO_ERR) {
        return res;
    }

    // ...
}

Now, whenever the Validate Phase is done for the <commit> operation, the callback function will be called and provide the access to the candidate and running datastores.

void agt_cb_sa_validate_complete_unregister(agt_cb_sa_validate_complete_t cbfn)

Unregister a SIL-SA Validate Complete callback.

This function unregisters a SIL-SA Validate Complete callback.

Parameters

cbfn -- address of SIL-SA callback function to use

Example Usage:

void interfaces_cleanup (void)
{
    // ...
    /* Unregister SIL-SA Validate Complete callback */

    agt_cb_sa_validate_complete_unregister(hooks_validate_complete_cb);

    // ...
}

SIL-SA Validate Complete Callback Example

The following example code shows a SIL-SA Validate Complete callback.

/********************************************************************
* FUNCTION hooks_validate_complete_cb
*
*  SIL-SA Validate Complete callback.
*
*
* RETURNS:
*   status
********************************************************************/
static status_t
    hooks_validate_complete_cb (const xmlChar *transaction_id_val)
{
    if (!transaction_id_val) {
        log_error("\ntransaction_id value not set");
        return ERR_INTERNAL_VAL;
    }

    const xmlChar *user = sil_sa_get_username();
    const xmlChar *client_addr = sil_sa_get_client_addr();
    status_t res = NO_ERR;

    if (LOGDEBUG2) {
        log_debug2("\n\n********************************************");
        log_debug2("\nEnter hooks_validate_complete_cb callback");
        log_debug2("\ntransaction_id -- %s", transaction_id_val);
        log_debug2("\nuser_id -- %s", user);
        log_debug2("\nclient_addr -- %s", client_addr);
        log_debug2("\n********************************************\n\n");
    }

    return res;

}  /* hooks_validate_complete_cb */

Apply Complete

The Apply Complete function is the user/system callback that is invoked after the Apply Phase has been processed during the <commit> operation.

If a callback fails the status of the failing callback is returned immediately and no further callbacks are made. As a result, the server will abort the commit.

Apply Complete Callback

The following function template definition is used for Apply Complete callback functions:

typedef status_t (*agt_cb_apply_complete_t)(ses_cb_t *scb, rpc_msg_t *msg, val_value_t *candidate, val_value_t *running)

Typedef of the agt_cb_apply_complete_t callback.

The Apply Complete callback is the user/system callback that is invoked after the Validate Phase has been processed during the <commit> operation.

The Validate Complete is object independent and module independent which means you don't have to link them to the specific object as it's done for EDIT or GET callbacks, and you don't have to link them to any specific module

Max Callbacks: No limit (except available heap memory)

Param scb

session control block making the request

Param msg

incoming rpc_msg_t in progress

Param candidate

candidate val_value_t for the config database to use

Param running

running val_value_t for the config database to use

Return

status

Apply Complete Callback Initialization and Cleanup

The Apply Complete callback function is regist