6 Plugins
Plugins are the “glue” that binds the underlying system to Clixon and where application semantics is added by an application developer.
The backend, CLI, Restconf and Netconf applications may use plugins, although the main use of plugins are the Backend section plugins.
Plugins are written in C as dynamically loaded modules (.so
files). At startup, the application looks in the assigned directory and loads all files with .so
suffixes from that dir in alphabetical order.
Plugins are located as follows:
Backend plugins are are in
CLICON_BACKEND_DIR
CLI plugins are are in
CLICON_CLI_DIR
NETCONF plugins are in
CLICON_NETCONF_DIR
RESTCONF plugins are in
CLICON_CLI_DIR
;
For example, to load all backend plugins from: /usr/local/lib/example/backend
:
<CLICON_BACKEND_DIR>/usr/local/lib/example/backend</CLICON_BACKEND_DIR>
- Callbacks are registered in two ways:
clixon_plugin_init : Return an API struct containing a fixed set of callbacks
register functions : Register callback functions for more complex interaction
Further, CLI callbacks as described in CLI section are special in the way that they are invoked from CLIgen specifications, not from the application itself.
6.1 Clixon_plugin_init
On startup, the application loads each plugin and calls
clixon_plugin_init
in that plugin. The function is expected to return an API
struct clixon_plugin_api
defining a set of static callbacks. The init
function may return NULL
in which case it is logged and ignored.
Once the plugin is loaded, it awaits callbacks from the application.
An example of a minimal plugin is as follows:
static clixon_plugin_api api = {
"example", /* Plugin name */
clixon_plugin_init, /* init - must be called clixon_plugin_init */
example_start, /* start */
example_exit, /* exit */
example_extension, /* yang extensions */
.ca_daemon=example_daemon, /* Plugin daemonized () (backend only) */
};
clixon_plugin_api *
clixon_plugin_init(clixon_handle h)
{
return &api;
}
First the clixon_plugin_api
struct defines the callbacks. There are two blocks of callbacks:
Common callbacks for backend, cli, netconf, and restconf
Application-specific callbacks. In the example, only
ca_daemon
is specific to backends. By far, the backend have most specific callbacks.
Second, the clixon_plugin_init()
function is defined and (at a minimum) returns the struct.
The following callbacks are common plugins for all Clixon applications:
- init
Clixon plugin init function, called immediately after plugin is loaded into the application. The name of the function must be called
clixon_plugin_init
. It returns a struct with the name of the plugin, and all other callback names.- start
Called when application is started and initialization is complete, but before the application is placed in the background and privileges are dropped (if applicable)
- exit
Called just before plugin is unloaded at exit
- extension
Called at parsing of yang modules containing an extension statement. A plugin may identify the extension by its name, and perform actions on the yang statement, such as transforming the yang in-memory. A callback is made for every statement, which means that several calls per extension can be made. See Misc section and the main example on how to use extensions.
- yang_mount
Called when populating a RFC8525 mount-point. See YANG section for more info.
- yang_patch
Patch a yang module. This may be necessary with an imported faulty or non-compliant YANG module
- errmsg
Customize a netconf error message for CLI return, log or debug messages. See Error section and the main example.
- version
Print a plugin-specific version stringh
See Backend section for backend specific callbacks, as well as corresponding manual sections for netconf/restconf/cli callbacks.
6.2 Registered callbacks
A second group of callbacks use register functions. This is a more detailed mechanism than the fixed callbacks described previously, but are only defined to a limited sets of functions:
rpc_callback_register()
- for user-defined RPC callbacks. Applicable for NETCONF, RESTCONF and backend.action_callback_register()
- for user-defined Action callbacks. Applicable for backend.upgrade_callback_register()
- for upgrading, see Upgrade section. Applicable only for backend.clixon_pagination_cb_register()
- for pagination, as described in Pagination section. Applicable only for backend.
A user may register may register a callback for an incoming RPC, and that function will be called.
There may be several callbacks for the same RPC. The order the callbacks are registered are as follows:
plugin_init
backend_rpc_init (where system callbacks are registered)
plugin_start
Which means if you register a copy-config callback in (1), it will be called before the system copy-config callback registered from (2) backend_rpc_init. If you register a copy-config in (3) plugin-start it will be called after the system copy-config.
Second, if there are more than one reply (eg <rpc-reply/><rpc-reply/>
) only the first reply will be parsed and used by the cli/netconf/restconf clients.
If you want to take the original and modify it, you should therefore register the callback in plugin_start (3) so that your callback will be called after the system RPC. Then you should modify the original reply (not add a new reply).
6.2.1 Example: RPC callback
This example shows how to define a new RPC in YANG for the backend, register a callback function in C, read and write a parameter. It is revised slightly from the main example.
YANG:
module clixon-example {
namespace "urn:example:clixon";
...
rpc example {
input {
leaf x {
type string;
...
output {
leaf y {
type string;
...
Register RPC in clixon_plugin_init():
clixon_plugin_api *clixon_plugin_init(clixon_handle h)
{
...
rpc_callback_register(h, example_rpc, NULL, "urn:example:clixon", "example");
Callback function reading value input x, modifying value and writing it as output value y:
static int
example_rpc(clixon_handle h, /* Clicon handle */
cxobj *xe, /* Request: <rpc><xn></rpc> */
cbuf *cbret, /* Reply eg <rpc-reply>... */
void *arg, /* client_entry */
void *regarg) /* Argument given at register */
{
char *val;
val = xml_find_body(xe, "x"); /* Read x value of incoming rpc */
cprintf(cbret, "<rpc-reply xmlns=\"%s\">", NETCONF_BASE_NAMESPACE);
val[0]++; /* Increment first char */
/* Construct reply */
cprintf(cbret, "<y xmlns=\"urn:example:clixon\">%s</y>", val);
cprintf(cbret, "</rpc-reply>");
Result netconf session:
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="42">
<example xmlns="urn:example:clixon">
<x>42</x>
</example>
</rpc>]]>]]>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="42">
<y xmlns="urn:example:clixon">52</y>
</rpc-reply>]]>]]>
6.2.2 Example: Action callback
This example follows RFC 7950 7.15.3.
An action is associated with a YANG node and can therefore not be registered at init, instead the start callback can be used, for example.
Register Action in example_reset():
int
example_start(clixon_handle h)
{
...
if (action_callback_register(h, ya, example_action_reset, NULL) < 0)
goto done;
6.3 Plugin callback guidelines
Note
This information is important to understand for the stability of clixon
The Clixon programs run as non-blocking single-threaded applications. It calls functions from within dynamically loaded modules. The callback code must be written with this programming model in mind. The behavior of the callback directly impacts the behavior of the caller and the whole system.
The most serious effect is when crash within a callback happens. This will cause the whole program to crash.
A more subtle problem is the environment of the program. Clixon will configure the environment, and it expects that the callback will return with the exact same environment intact. If you change a signal handler, a terminal configuration, etc. you must restore the state as it was on entry. Failure to do this can cause problems that are difficult to isolate and fix.
A list of things to watch out for (but not complete!):
a crash in the plugin
change of signal behaviour, such as blocking or assigning signal handlers
change of terminal settings (for CLI callbacks)
change of process privileges
asynchronous calls
If you fork or create threads, ensure the main program continues uninterrupted
The following config option is related to checking callbacks:
- CLICON_PLUGIN_CALLBACK_CHECK
Enable check of resources before and after each callback. Checks are currently limited to signal and terminal settings