#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.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.
#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
DataSource
class then several methods inRRDDataSource
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 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
COMMAND
data 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
DERIVE
with a minimum of 0 will recordunknown
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.
#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%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?
#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.sh
For example:
${here/ZenPackManager .....}.../MyCollectorPlugin.sh ${dev/manageIp} ${dev/zSnmpCommunity} OtherParameters
After 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
HttpMonitor
is installed and thenZenPacks.zenoss.HttpMonitor
is installed (which has prevZenPackName=HttpMonitor) thenZenPacks.zenoss.HttpMonitor
will replaceHttpMonitor
. All packable objects in the database that are included inHttpMonitor
will be added toZenPacks.zenoss.HttpMonitor
instead. A migrate script is usually required to set __class__ correctly on instances of ZenPack-provided classes in the object database. TheZenPacks.zenoss.HttpMonitor
ZenPack has an example of this in itsmigrate
directory, in theConvertHttpMonitorDataSources.py
file.