.. -*- mode: rst -*-

.. _development-plugins:

Bcfg2 Plugin development
========================

While the Bcfg2 server provides a good interface for representing
general system configurations, its plugin interface offers the ability
to implement configuration interfaces and representation tailored to
problems encountered by a particular site. This chapter describes what
plugins are good for, what they can do, and how to implement them.

Several plugins themselves have pluggable backends, and for narrow
cases you may want to develop a backend for an existing plugin rather
than an entirely new plugin.  See the following pages for more
information:

.. toctree::
   :maxdepth: 1

   cfg
   packages


Bcfg2 Plugins
-------------

Bcfg2 plugins are loadable python modules that the Bcfg2 server loads at
initialization time. These plugins can contribute to the functions already
offered by the Bcfg2 server or can extend its functionality. In general,
plugins will provide some portion of the configuration for clients, with a
data representation that is tuned for a set of common tasks. Much of the
core functionality of Bcfg2 is implemented by several plugins, however,
they are not special in any way; new plugins could easily supplant one
or all of them.

.. automodule:: Bcfg2.Server.Plugin
   :no-members:

Server Plugin Types
-------------------

A plugin must implement at least one of the interfaces described
below.  Each interface is available as a class in
:mod:`Bcfg2.Server.Plugin`.  In most cases, a plugin must also
inherit from :class:`Bcfg2.Server.Plugin.base.Plugin`, which is the
base Plugin object (described below).  Some of the interfaces listed
below are themselves Plugin objects, so your custom plugin would only
need to inherit from the plugin type.

Plugin
^^^^^^

.. autoclass:: Bcfg2.Server.Plugin.base.Plugin
   :members: name, __author__, experimental, deprecated, conflicts,
             sort_order, __rmi__, init_repo, shutdown
   :inherited-members:
   :show-inheritance:

With the exceptions of
:class:`Bcfg2.Server.Plugin.interfaces.Statistics` and
:class:`Bcfg2.Server.Plugin.interfaces.ThreadedStatistics`, the plugin
interfaces listed below do **not** inherit from Plugin; they simply
provide interfaces that a given plugin may or must implement.

Interfaces
^^^^^^^^^^

.. class:: Bcfg2.Server.Plugin.interfaces
.. automodule:: Bcfg2.Server.Plugin.interfaces

Exposing XML-RPC Functions
--------------------------

Plugins can expose XML-RPC functions that can then be called with
:ref:`bcfg2-admin xcmd <server-admin-xcmd>`.  Note that there is
absolutely no access control beyond the initial authentication, so
take care to not expose any data or behavior via XML-RPC that you
would not want all of your clients to be able to see or use.

To expose a function, simply add its name to the ``__rmi__`` class
attribute.  (RMI stands for "Remote Method Invocation.")  Consider
this example from the :ref:`server-plugins-generators-packages`
plugin:

.. code-block:: python

    class Packages(Bcfg2.Server.Plugin.Plugin,
                   Bcfg2.Server.Plugin.StructureValidator,
                   Bcfg2.Server.Plugin.Generator,
                   Bcfg2.Server.Plugin.Connector,
                   Bcfg2.Server.Plugin.ClientRunHooks):
        name = 'Packages'
        conflicts = ['Pkgmgr']
        __rmi__ = Bcfg2.Server.Plugin.Plugin.__rmi__ + ['Refresh', 'Reload']

    def Refresh(self):
        self._load_config(force_update=True)
        return True

    def Reload(self):
        self._load_config()
        return True

This exposes two functions, ``Refresh`` and ``Reload``, in addition to
any default methods that are already exposed.  To call one of these
functions, you could run::

    bcfg2-admin xcmd Packages.Refresh

Invalidating Caches
-------------------

.. versionadded:: 1.3.0

In Bcfg2 1.3.0, some limited :ref:`server-caching` was introduced.  If
you are writing a :class:`Bcfg2.Server.Plugin.interfaces.Connector`
plugin that implements
:func:`Bcfg2.Server.Plugin.interfaces.Connector.get_additional_groups`,
then you need to be able to invalidate the server metadata cache in
order to be compatible with the ``cautious`` or ``aggressive`` caching
modes.

The two attributes you need to know about are:

* :attr:`Bcfg2.Server.Core.metadata_cache_mode`: A string description
  of the caching mode.  See :ref:`server-caching` for a description of
  each mode.
* :attr:`Bcfg2.Server.Core.metadata_cache`: A dict-like
  :class:`Bcfg2.Cache.Cache` object that stores the cached data.

:class:`Bcfg2.Server.Plugin.base.Plugin` objects have access to the
:class:`Bcfg2.Server.Core` object as ``self.core``.  In general,
you'll be interested in the :func:`Bcfg2.Cache.Cache.expire` method;
if called with no arguments, it expires all cached data; if called
with one string argument, it expires cached data for the named client.

It's important, therefore, that your Connector plugin can either track
when changes are made to the group membership it reports, and expire
cached data appropriately when in ``cautious`` or ``aggressive`` mode;
or prudently flag an incompatibility with those two modes.

For examples, see:

* :func:`Bcfg2.Server.Plugins.Probes.ReceiveData` takes a copy of the
  groups that have been assigned to a client by
  :ref:`server-plugins-probes-index`, and if that data changes when
  new probe data is received, it invalidates the cache for that
  client.
* :func:`Bcfg2.Server.Plugins.GroupPatterns.Index` expires the entire
  cache whenever a FAM event is received for the
  :ref:`server-plugins-grouping-grouppatterns` config file.
* :func:`Bcfg2.Server.Plugins.PuppetENC.end_client_run` expires the
  entire cache at the end of every client run and produces a message
  at the warning level that the
  :ref:`server-plugins-connectors-puppetenc` plugin is incompatible
  with aggressive caching.

Tracking Execution Time
-----------------------

.. versionadded:: 1.3.0

Statistics can and should track execution time statistics using
:mod:`Bcfg2.Statistics`.  This module tracks execution time for the
server core and for plugins, and exposes that data via ``bcfg2-admin
perf``.  This data can be invaluable for locating bottlenecks or other
performance issues.

The simplest way to track statistics is to use the
:func:`Bcfg2.Server.Plugin.helpers.track_statistics` decorator to
decorate functions that you would like to track execution times for:

.. code-block:: python

    from Bcfg2.Server.Plugin import track_statistics

    @track_statistics()
    def do_something(self, ...):
        ...

This will track the execution time of ``do_something``.

More granular usage is possible by using :func:`time.time` to manually
determine the execution time of a given event and calling
:func:`Bcfg2.Statistics.Statistics.add_value` with an appropriate
statistic name.

Bcfg2.Statistics
^^^^^^^^^^^^^^^^

.. automodule:: Bcfg2.Statistics

Plugin Helper Classes
---------------------

.. automodule:: Bcfg2.Server.Plugin.helpers
   :inherited-members:

.. Debuggable is in base to avoid circular imports, but it's a helper
.. and should be listed here in the docs

.. autoclass:: Bcfg2.Server.Plugin.base.Debuggable
   :inherited-members:

Plugin Exceptions
-----------------

.. automodule:: Bcfg2.Server.Plugin.exceptions

See Also
--------

* :ref:`development-compat`
* :ref:`development-utils`
