$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
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.
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.
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.
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.
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.
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.
Don't include a number in your threshold's name.
This makes people have to recreate the threshold if they want to change it.
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.
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?
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
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.
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:
This is the version specification representing the required Zenoss version from the ZenPack's Edit page.
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.