Developer's Guide

  • Docs Home
  • Community Home

4. Developing the ZenPack

4.1. Base ZenPack Class

$ZENHOME/Products/ZenModel/ZenPack.py contains the base ZenPack class. When a ZenPack is installed Zenoss inspects YourZenPackId/ZenPacks/..../LastPartOfName/__init__.py to see if it contains a class named ZenPack. If it does then Zenoss instantiates it, otherwise Zenoss instantiates the base ZenModel.ZenPack.ZenPack class. That instance is then added to the dmd.ZenPackManager.packs tree.

There are several attributes and methods of ZenPack that subclasses might be interested in overriding:

Interesting ZenPack properties and methods

packZProperties

is a mechanism for easily adding zProperties. packZProperties is a list of tuples, with each tuple containing three strings in this order:

  • name of the zProperty

  • default value of the zProperty

  • type of the zProperty (such as string or int)

Zenoss will automatically create these when the ZenPack is installed and remove them when the ZenPack is removed. See ZenPacks.zenoss.MySqlMonitor for an example of this usage.

install(self, app)

parais called when the ZenPack is installed. If you override this be sure to call the inherited method within your code.

remove(self, app, leaveObjects)

is called when the ZenPack is removed. As with install(), make sure you call the inherited method if you override.

4.2. Storing Objects in the ZODB

ZenPacks can provide Python classes for objects that will be stored in the object database. The most frequent example of this is DataSource subclasses. When a ZenPack is removed those classes are no longer accessible so the objects in the database are broken. (Zeo needs to have the appropriate Python class in order to unpickle an object from the database.) In previous versions of Zenoss there was not an easy way to associate instances of a ZenPack-provided class with the ZenPack that provided the class. As a result ZenPack removal could easily cause broken objects to remain in the database. If Zope had already loaded a class into the interpreter the objects in question might continue to function until Zope was restarted, making diagnosis of such problems even more difficult.

In Zenoss 2.2 the ZenPackPersistance class aims to remedy this problem. Any Python class provided by a ZenPack should subclass the ZenModel.ZenPackPersistence.ZenPackPersistence class. Zenoss maintains a catalog of all ZenPackPersistence instances in the database. When a ZenPack is removed, the catalog is queried to determine which objects need to be deleted. Any ZenPack-provided Python class that might be instantiated in the object database should subclass ZenPackPersistence and define ZENPACKID in the class as the name of the ZenPack providing the class. For an example of this see the ZenPacks.zenoss.MySqlMonitor.datasources.MySqlMonitorDataSource ZenPack.

4.3. Providing DataSource classes

ZenPacks can provide new classes of DataSources by subclassing the ZenModel.RRDDataSource.RRDDataSource class. If you include only one DataSource class per file, name the modules after the class the contain (ie MyDataSource.py contains the class MyDataSource), and place those modules in the ZenPack's data sources directory then they will automatically be discovered by Zenoss. If you wish to customize this behavior take a look at the ZenPack.getDataSourceClasses() function. See the ZenPacks.zenoss.HttpMonitor and ZenPacks.zenoss.MySqlMonitor ZenPacks for examples of ZenPacks that provide custom DataSource classes.

When creating a custom DataSource class one of the first decisions you have to make is whether you want zencommand to process these DataSources for you or whether you will provide a custom collector daemon to process them. The zencommand daemon is a very versatile mechanism for executing arbitrary commands either on the Zenoss server or on the device being monitored, processing performance data returned by the DataSource and generating events in Zenoss as appropriate. zencommand expects the command it executes be compatible with the Nagios plug-in API. Specifically two aspects of that API are of most importance:

  • Return code -The command should exit with a return code of 0, 1, 2 or 3. See here in the Nagios plug-in API for more detail.

  • Performance data -- If the command returns performance data then that data can be pulled into Zenoss by creating DataPoints with the same names used in the command output. See here in the Nagios plug-in API for more detail.

    If you want zencommand to handle instances of your custom DataSource class then several methods in RRDDataSource are of particular interest:

  • getDescription(self) - This returns a string describing the DataSource instance. This string is displayed next to the DataSource on the RRDTemplate view page.

  • getCommand(self, context, cmd=None) - This returns the string that is the command for zencommand to execute. context is the device or component to be collected. If you need to evaluate TALES expressions in the command to replace things like ${dev/id} and so forth you can call the parent class's getCommand() and pass your command as the cmd argument. (cmd will not be passed into your method, it exists specifically for subclasses to pass their commands to the parent for TALES evaluation.)

  • checkCommandPrefix(self, context, cmd) - Zenoss will check the string you return from getCommand() to see if it is a relative or absolute path to a command. If the string starts with '/' or '$' then Zenoss assumes it is absolute. Otherwise the zProperty zCommandPath from the context is prepended to the cmd string. You can override checkCommandPrefix() if you wish to alter this behavior.

Make sure that your DataSource subclasses also subclass ZenPackPersistence and list it first among the parent classes. See the section on ZenPackPersistence.py for more details.

4.4. Performance Template Checklist

Performance templates are one of the easiest places to make a real user experience difference when new features are added to Zenoss. Spending a very small amount of time to get the templates right goes a long way towards improving the overall user experience.

4.4.1. Data Sources

  • Can your data source be named better?

    • Is it a common metric that is being collected from other devices in another way? If so, name yours the same. This makes global reporting much easier.

    • camelCaseNames are the standard. Use them.

  • Never use absolute paths for COMMAND data source command templates. This will end up causing problems on one of the three platforms we deal with. Link your plugin into zenPath('libexec') instead.

4.4.2. Data Points

  • Using a COUNTER? You might want to think otherwise.

    • Unnoticed counter rollovers can result in extremely skewed data.

    • Using a DERIVE with a minimum of 0 will record unknown instead of wrong data.

  • Enter the minimum and/or maximum possible values for the data point if you know them.

    • This again will allow unknown to be recorded instead of bad data.

4.4.3. Thresholds

  • Don't include a number in your threshold's name.

    • This makes people have to recreate the threshold if they want to change it.

4.4.4. Graph Definitions

  • Have you entered the units? Do it!

    • This will become the y-axis label and should be all lowercase.

    • Always use the base units. Never kbps or MBs. bps or bytes are better.

  • Do you know the minimum/maximum allowable values? Enter them!

    • Common scenarios include percentage graphing with minimum 0 and maximum 100.

    • Think about the order of your graph points. Does it make sense?

    • Are there other templates that show similar data to yours? If so, you should try hard to mimic their appearance to create a consistent experience.

4.4.5. Graph Points

  • Have you changed the legend? Do it!

  • Adjust the format so that it makes sense.

    • %5.2lf%s is good for values you want RRDTool to auto-scale.

    • %6.2lf%% is good for percentages.

    • %4.0lf is good for four digit numbers with no decimal precision or scaling.

  • Should you be using areas or lines?

    • Lines are good for most values.

    • Areas are good for things that can be thought of as a volume or quantity.

  • Does stacking the values to present a visual aggregate make sense?

4.5. Providing Performance Collector Plugins

When providing performance collectors in a ZenPack (for example, Nagios-style plugins), the suggested method for referencing the collector in the Command Template area is the following TALES expression:

${here/ZenPackManager/packs/ZenPacks.pkg.zpid/path}/libexec/myplugin.sh
  

4.6. Providing Daemons

ZenPacks can provide new performance collectors and event monitors. This is a somewhat complex undertaking, so before deciding to write your own daemons make sure that zencommand and a custom DataSource class won't fit your needs (see Section 4.3, “Providing DataSource classes” above.) Any file in a ZenPack's daemons directory is symlinked in $ZENHOME/bin when the ZenPack is installed. Also, the Zenoss script that controls the core daemons will attempt to manage your daemon too. So a zenoss start, for example, will attempt to start your daemon as well as the core daemons.

Custom daemons usually subclass the ZenHub.PBDaemon.PBDaemon class. This class provides the basic framework for communicating with zenhub. See the section "Writing a Zenoss Performance Collector" for more details.

4.7. setuptools and the zenpacksupport

Zenoss requires a Python module called setuptools to create and install eggs. The setuptools module is installed by the Zenoss installer in the $ZENHOME/lib/python directory. Zenoss also provides a module named zenpacksupport which extends setuptools. The zenpacksupport class defines additional metadata that is written to and read from ZenPack eggs. This metadata is provided through additional options passed to the setup() call in a ZenPack's setup.py file. Those arguments are:

compatZenossVers

This is the version specification representing the required Zenoss version from the ZenPack's Edit page.

prevZenPackName

This is the name of the old-style (non-egg) ZenPack that this ZenPack replaces. If a ZenPack with this name is installed in Zenoss then it is upgraded and replaced when this ZenPack is installed. For example, if HttpMonitor is installed and then ZenPacks.zenoss.HttpMonitor is installed (which has prevZenPackName=HttpMonitor) then ZenPacks.zenoss.HttpMonitor will replace HttpMonitor. All packable objects in the database that are included in HttpMonitor will be added to ZenPacks.zenoss.HttpMonitor instead. A migrate script is usually required to set __class__ correctly on instances of ZenPack-provided classes in the object database. The ZenPacks.zenoss.HttpMonitor ZenPack has an example of this in its migrate directory, in the ConvertHttpMonitorDataSources.py file.