#3.4. Developing the ZenPack
#3.4.1. Base ZenPack Class
$ZENHOME/Products/ZenModel/ZenPack.py contains the base ZenPack class. When a ZenPack is installed Zenoss inspects to see if it contains a class named YourZenPackId/ZenPacks/..../LastPartOfName/__init__.pyZenPack. 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.MySqlMonitorfor 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.
#3.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.
#3.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
DataSourceclass then several methods inRRDDataSourceare of particular interest:getDescription(self) - This returns a string describing the
DataSourceinstance. 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 overridecheckCommandPrefix()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.
#3.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.
#3.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
COMMANDdata source command templates. This will end up causing problems on one of the three platforms we deal with. Link your plugin intozenPath('libexec')instead.
#3.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
DERIVEwith a minimum of 0 will recordunknowninstead of wrong data.
Enter the minimum and/or maximum possible values for the data point if you know them.
This again will allow
unknownto be recorded instead of bad data.
#3.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.
#3.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.
#3.4.4.5. Graph Points
Have you changed the legend? Do it!
Adjust the format so that it makes sense.
%5.2lf%sis good for values you want RRDTool to auto-scale.%6.2lf%%is good for percentages.%4.0lfis 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?
#3.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
#3.4.6. Referencing Collector Plugins in ZenPacks
While modeler plugins are stored in the ZenPack's modeler/plugins directory, collector plugins are, by convention, stored in the libexec directory. Because Zenoss can be installed in multiple ways, and a ZenPack's directory name, when installed, includes a version number, Zenoss offers a more portable and "future-proof" way of referencing a plugin.
In the Command Template section of the data source, you can reference a plugin by using a TALES expression, such as:
${here/ZenPackManager .....}.../file.shFor example:
${here/ZenPackManager .....}.../MyCollectorPlugin.sh ${dev/manageIp} ${dev/zSnmpCommunity} OtherParametersAfter adding the performance template containing the data source to a ZenPack, and then exporting the ZenPack, the ZenPack's object/objects.xml file will contain an entry similar to:
<property .....>
${here/ZenPackManager .....}.../MyCollectorPlugin.sh ${dev/manageIp} ${dev/zSnmpCommunity} OtherParameters </property>#3.4.7. 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 3.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.
#3.4.8. 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
HttpMonitoris installed and thenZenPacks.zenoss.HttpMonitoris installed (which has prevZenPackName=HttpMonitor) thenZenPacks.zenoss.HttpMonitorwill replaceHttpMonitor. All packable objects in the database that are included inHttpMonitorwill be added toZenPacks.zenoss.HttpMonitorinstead. A migrate script is usually required to set __class__ correctly on instances of ZenPack-provided classes in the object database. TheZenPacks.zenoss.HttpMonitorZenPack has an example of this in itsmigratedirectory, in theConvertHttpMonitorDataSources.pyfile.



