Configuration and commands in BIND10

Introduction
Overview
C++ API for modules
Python API for modules
Specification files


Introduction
------------

One of the goals of BIND 10 was to have 'live' configuration; to be able to change settings on the fly, and have the system remember those settings automatically, much like, for instance, a router operates.

In order for this to work, it is important that all modules have a way of dynamically handling configuration updates. The way this works is explained in this document.


Overview
--------

Central to the configuration part is the Configuration Manager b10-cfgmgr. The configuration manager loads any existing configuration, receives configuration updates from user interfaces, and notifies modules about configuration updates.

UI  <---UIAPI---> Configuration Manager <---ModuleAPI---> Module
                                        <---ModuleAPI---> Module
                                        <---ModuleAPI---> Module

When a configuration changes comes in from a UI, the configuration manager
sends a message to the channel of the module of which the configuration is a part of.
Through the Module API, the module automatically picks this up, and validates it.

If it doesn't validate, an error is sent back to the manager, and subsequently to the UI (though this is not implemented yet).

If it does, a callback function specified by the module itself is called, with the a MapElement containing the configuration items that have changed.

The callback function returns a message containing either success or failure, and on success, the new configuration is locally stored in the modules session. Plans are to make this optional, so that modules have two choices; they can have the configuration stored for random access later, or they can run through the configuration when there is a changes, modify their internal structures, and then drop the full configuration. This makes handling configuration updates more complicated, but is more efficient assuming that configuration values are much more often read than written.

Commands are handled in a similar way, but do not go through the configuration manager.


C++ API For modules
-------------------

The important class for modules is isc::config::ModuleCCSession; this is the class that manages the connection to the command channel, stores the current configuration, validates new configurations, and calls callback functions as needed.
[link to ModuleCCSession doxygen html]

Upon initialization, the module provides it with a path to a specification file. This specification file contains information about the module, the configuration option the module has, and the direct commands the modules accepts. See the chapter 'specification files' for more information on these.

The module also needs to provide two callback functions; one for handling configuration updates, and one for handling commands.

The function for handling configuration updates has the following signature:
isc::data::ElementPtr my_config_handler(isc::data::ElementPtr new_config);
[link to Element doxygen html]

The new_config is a ElementPtr pointing to a MapElement containing data in the form as specified by the specification file. It only contains values that were changed.

The module can walk through this set and alter its behaviour accordingly if necessary. It can also simply check them and return success (see below) and reference the needed configuration values directly when necessary by calling get_config_value(std::string identifier).

The callback function must return an answer message, which is created with isc::config::createAnswer(). For successful handling of the configuration, it should return the result of createAnswer(0) (0 being the result code for success). If there is a problem, the function can return the result of createAnswer(non-zero, "string_with_error_message"). In this case, the new configuration is not stored, and the error is fed back to the configuration manager.

Direct commands work much the same way, only in this case the answer returned can also contain an ElementPtr with data specific to the command.

The checking of new commands or configuration updates must be done manually, with the checkCommand() function. If this function is called, the moduleccsession class checks the command channel for any relevant messages, and handles them accordingly.


Python API for modules
----------------------

The class to use in python modules is isc.config.ccsession.ModuleCCSession
[link to doxygen python version]

Again, the module initializes it with the path to a specification file, and two callback functions.

It works much the same as the C++ version.

The callback function for configuration updates has the form
answer my_config_handler(new_config)

Since python has dynamic typing, there is no specific class for the data that is passed to the handler, but it is a dict containing data as specified in the specification file.

There are however a few convenience functions that can be found in the isc.config.data module.

The answer can be created with isc.config.ccsession.create_answer(rcode, message), where rcode=0 is interpreted as success, and message can contain an error message for rcode!=0 results.

The function to trigger update checks is check_command()


Specification files
-------------------

There is only 1 real mandatory element in the specification, and that is the name of the module.

The simplest specification file therefore looks like this:
{
  "module_spec": {
    "module_name": "my_module"
  }
}

This is the specification for a module that has no commands and no configuration options.

my_module is the name of the module, and also the name of the command channel it will automatically subscribe to.

To add a simple configuration option, let's say an int, we make it the following:
{
  "module_spec": {
    "module_name": "my_module"
    "config_data": [
      { "item_name": "some_number",
        "item_type": "integer",
        "item_optional": False,
        "item_default": 123
      }
    ]
  }
}

"config_data" contains a list of elements of the form
{ "item_name": "name"
  "item_type": "integer|real|boolean|string|list|map"
  "item_optional": True|False
  "item_default": <depends on type>
}

You can provide as much options as you need, and can also make compound elements
through "list_item_spec" for lists and "map_item_spec" for maps. See [link] for the
full documentation on specification files.
