summaryrefslogtreecommitdiffstats
path: root/doc/development/index.txt
blob: 4e2f29a5479d78740cbf12225c02cd865efc8216 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
.. -*- mode: rst -*-

.. _development-index:

Bcfg2 Development
=================

There are many ways to get involved in Bcfg2 development. Here we will
outline some things that can help you get familiar with the various
areas of the Bcfg2 code.

Tips for Bcfg2 Development
--------------------------

#. Focus on either the client or server code. This focuses the development process down to the precise pieces of code that matter for the task at hand.

   * If you are developing a client driver, then write up a small configuration specification that includes the needed characteristics.
   * If you are working on the server, run bcfg2-info and use to assess the code

#. Use the python interpreter. One of python's most appealing features is interactive use of the interpreter.

   * If you are developing for the client-side, run ``python -i /usr/sbin/bcfg2`` with the appropriate bcfg2 options. This will cause the python interpreter to continue running, leaving all variables intact. This can be used to examine data state in a convenient fashion.
   * If you are developing for the server side, use bcfg2-info and the "debug" option. This will leave you at a python interpreter prompt, with the server core loaded in the variable "bcore".

#. Use pylint obsessively. It raises a lot of style-related warnings which can be ignored, but most all of the errors are legitimate.
#. If you are doing anything with Regular Expressions, [http://kodos.sourceforge.net/ Kodos - The Python Regular Expression Debugger] and [http://re-try.appspot.com/ re-try] are your friends.

Environment setup for development
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

* Check out a copy of the code::

      svn co https://svn.mcs.anl.gov/repos/bcfg/trunk/bcfg2

* Create link to src/lib::

      cd bcfg2
      ln -s src/lib Bcfg2

* Add ``bcfg2/src/sbin`` to your PATH environment variable
* Add ``bcfg2`` to your PYTHONPATH environment variable

Writing A Client Tool Driver
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This page describes the step-by-step process of writing a client tool
driver for a configuration element type. The included example describes
an existing driver, and the process that was used to create it.

#. Pick a name for the driver. In this case, we picked the name RPM.
#. Add "RPM" to the __all__ list in src/lib/Client/Tools/__init__.py
#. Create a file in src/lib/Client/Tools with the same name (RPM.py)
#. Create a class in this file with the same name (class RPM)

   * If it handles Package entries, subclass Bcfg2.Client.Tools.PkgTool (from here referenced as branch [P])
   * If it handles Service entries, subclass Bcfg2.Client.Tools.SvcTool (from here referenced as branch [S])
   * Otherwise, subclass Bcfg2.Client.Tools.Tool (from here referenced as branch [T])

#. Set __name__ to "RPM"
#. Add any required executable programs to __execs__
#. Set __handles__ to a list of (entry.tag, entry.get('type')) tuples. This determines which entries the Tool module can be used on. In this case, we set __handles__ = [('Package', 'rpm')].
#. Add verification. This method should return True/False depending on current entry installation status.

   * [T] Add a Verify<entry.tag> method.
   * [P] Add a VerifyPackage method.
   * [S] Add a VerifyService method.
   * In the failure path, the current state of failing entry attributes should be set in the entry, to aid in auditing. [[BR]] (For example, if a file should be mode 644, and is currently mode  600, then set attribute current_perms='600' in the input entry)

#. Add installation support. This method should return True/False depending on the results of the installation process.

   * [T,S] Add an Install<entry.tag> method.
   * [P] The PkgTool baseclass has a generic mechanism for performing all-at-once installations, followed, in the case of failures, by single installations. To enable this support, set the pkgtype attribute to the package type handled by this driver. Set the pkgtool to a tuple ("command string %s", ("per-package string format", [list of package entry fields])). For RPM, we have setup pkgtool =  ("rpm --oldpackage --replacepkgs --quiet -U %s", ("%s", ["url"]))

#. Implement entry removal

   * [T,S] Implement a Remove method that removes all specified entries (prototype Remove(self, entries))
   * [P] Implement a !RemovePackages that removes all specified entries (same prototype as Remove)

#. Add a FindExtra method that locates entries not included in the configuration. This may or may not be required, certain drivers do not have the capability to find extra entries.
#. [P] Package drivers require a !RefreshPackages method that updates the internal representation of the package database.

Writing Tool Driver Methods
"""""""""""""""""""""""""""

#. Programs can be run using self.cmd.run. This function returns a (return code, stdout list) tuple.
#. The configuration is available as self.config
#. Runtime options are available in a dictionary as self.setup
#. Informational, error, and debug messages can be produced by running self.logger.info/error/debug.

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.

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.

The following table describes the various functions of bcfg2 plugins.

+--------------------+---------------------------------------------+
| Name               | Description                                 |
+====================+=============================================+
| Probes             | Plugins can issue commands to collect       |
|                    | client-side state (like hardware inventory) |
|                    | to include in client configurations         |
+--------------------+---------------------------------------------+
| ConfigurationEntry | Plugins can construct a list of per-client  |
| List               | configuration entry lists to include in     |
|                    | client configurations.                      |
+--------------------+---------------------------------------------+
| ConfigurationEntry | Literal values for configuration entries    |
| contents           |                                             |
+--------------------+---------------------------------------------+
| XML-RPC functions  | Plugins can export function calls that      |
|                    | expose internal functions.                  |
+--------------------+---------------------------------------------+

Writing Bcfg2 Plugins
^^^^^^^^^^^^^^^^^^^^^

Bcfg2 plugins are python classes that subclass from
Bcfg2.Server.Plugin.Plugin. Several plugin-specific values must be set
in the new plugin. These values dictate how the new plugin will behave
with respect to the above four functions.  The following table describes
all important member fields.

+-----------------+-----------------------------------+--------------------------+
| Name            | Description                       | Format                   |
+=================+===================================+==========================+
| __name__        | The name of the plugin            | string                   |
+-----------------+-----------------------------------+--------------------------+
| __version__     | The plugin version (generally     | string                   |
|                 | tied to revctl keyword expansion) |                          |
+-----------------+-----------------------------------+--------------------------+
| __author__      | The plugin author.                | string                   |
+-----------------+-----------------------------------+--------------------------+
| __author__      | The plugin author.                | string                   |
+-----------------+-----------------------------------+--------------------------+
| __rmi__         | Set of functions to be exposed as | List of function names   |
|                 | XML-RPC functions                 | (strings)                |
+-----------------+-----------------------------------+--------------------------+
| Entries         | Multidimentional dictionary of    | Dictionary of            |
|                 | keys that point to the function   | ConfigurationEntityType, |
|                 | used to bind literal contents for | Name keys, and function  |
|                 | a given configuration entity.     | reference values         |
+-----------------+-----------------------------------+--------------------------+
| BuildStructures | Function that returns a list of   | Member function          |
|                 | the structures for a given client |                          |
+-----------------+-----------------------------------+--------------------------+
| GetProbes       | Function that returns a list of   | Member function          |
|                 | probes that a given client should |                          |
|                 | execute                           |                          |
+-----------------+-----------------------------------+--------------------------+
| ReceiveData     | Function that accepts the probe   | Member function          |
|                 | results for a given client.       |                          |
+-----------------+-----------------------------------+--------------------------+

Example Plugin
^^^^^^^^^^^^^^

.. code-block:: python

    import Bcfg2.Server.Plugin
    class MyPlugin(Bcfg2.Server.Plugin.Plugin):
       '''An example plugin'''
       # All plugins need to subclass Bcfg2.Server.Plugin.Plugin
       __name__ = 'MyPlugin'
       __version__ = '1'
       __author__ = 'me@me.com'
       __rmi__ = ['myfunction']
       # myfunction is not available remotely as MyPlugin.myfunction

       def __init__(self, core, datastore):
           Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
           self.Entries = {'ConfigFile':{'/etc/foo.conf': self.buildFoo}}

       def myfunction(self):
           '''function for xmlrpc rmi call'''
           #do something
           return True

       def buildFoo(self, entry, metadata):
           '''Bind per-client information into entry based on metadata'''
           entry.attrib.update({'owner':'root', 'group':'root', 'perms':'644'})
           entry.text = '''contents of foo.conf'''

Example Connector
^^^^^^^^^^^^^^^^^

.. code-block:: python

    import Bcfg2.Server.Plugin

    class Foo(Bcfg2.Server.Plugin.Plugin,
             Bcfg2.Server.Plugin.Connector):
        '''The Foo plugin is here to illustrate a barebones connector'''
        name = 'Foo'
        version = '$Revision: $'
        experimental = True

        def __init__(self, core, datastore):
            Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
            Bcfg2.Server.Plugin.Connector.__init__(self)
            self.store = XMLFileBacked(self.data, core.fam)

        def get_additional_data(self, metadata):

            mydata = {}
            for data in self.store.entries['foo.xml'].data.get("foo", []):

                mydata[data] = "bar"

            return  dict([('mydata', mydata)])

        def get_additional_groups(self, meta):
            return self.cgroups.get(meta.hostname, list())

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

Generator
^^^^^^^^^

Generator plugins contribute to literal client configurations

Structure
^^^^^^^^^

Structure Plugins contribute to abstract client configurations

Metadata
^^^^^^^^

Signal metadata capabilities

Connector
^^^^^^^^^

Connector Plugins augment client metadata instances

Probing
^^^^^^^

Signal probe capability

Statistics
^^^^^^^^^^

Signal statistics handling capability

Decision
^^^^^^^^

Signal decision handling capability

Version
^^^^^^^

Interact with various version control systems

Writing Server Plugins
----------------------

Metadata
^^^^^^^^

If you would like to define your own Metadata plugin (to extend/change
functionality of the existing Metadata plugin), here are the steps to
do so. We will call our new plugin `MyMetadata`.

#. Add MyMetadata.py

   .. code-block:: python

       __revision__ = '$Revision$'

       import Bcfg2.Server.Plugins.Metadata

       class MyMetadata(Bcfg2.Server.Plugins.Metadata.Metadata):
           '''This class contains data for bcfg2 server metadata'''
           __version__ = '$Id$'
           __author__ = 'bcfg-dev@mcs.anl.gov'

       def __init__(self, core, datastore, watch_clients=True):
           Bcfg2.Server.Plugins.Metadata.Metadata.__init__(self, core, datastore, watch_clients)

#. Add MyMetadata to ``src/lib/Server/Plugins/__init__.py``
#. Replace Metadata with MyMetadata in the plugins line of bcfg2.conf

.. toctree::
   :maxdepth: 1