13.07.2015 Views

Zenoss Labs Documentation - Read the Docs

Zenoss Labs Documentation - Read the Docs

Zenoss Labs Documentation - Read the Docs

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>Release<strong>Zenoss</strong> <strong>Labs</strong>June 18, 2014


Contents1 Contents 21.1 ZenPack Development Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.2 ZenPack <strong>Documentation</strong> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 641.3 ZenPack Taxonomy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 782 <strong>Documentation</strong> Formats 893 Contribution 904 Contact 91i


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseHerein lies documentation created by <strong>Zenoss</strong> <strong>Labs</strong>. This will have to do until a better home can be found.Contents 1


CHAPTER 1Contents1.1 ZenPack Development GuideWelcome to <strong>the</strong> ZenPack Development Guide. The purpose of this guide is to walk you through <strong>the</strong> creation of aZenPack. We will be focusing on solving specific problems ra<strong>the</strong>r than attempting to provide an exhaustive list of <strong>the</strong>capabilities of a ZenPack.Contents:1.1.1 PrerequisitesTo follow <strong>the</strong> steps in this guide you will need to have access to <strong>the</strong> following:• A Linux server with <strong>Zenoss</strong> installed on it. This should not be a <strong>Zenoss</strong> server you care about. We will breakthings. You can download <strong>Zenoss</strong> from <strong>the</strong> <strong>Zenoss</strong> download site.• An SSH client to connect to your <strong>Zenoss</strong> server. PuTTY works well for Windows, ssh from <strong>the</strong> command lineworks well for Mac and Linux.• This guide.This guide will provide full examples to create a working ZenPack. However, <strong>the</strong> examples and <strong>the</strong> explanations for<strong>the</strong>m will be much easier to understand with at least basic skills in <strong>the</strong> following areas:• Linux: Ability to move around <strong>the</strong> file system, manage files and run commands.• Programming or scripting with Python experience being a plus.• XML syntax.• SNMP.Finally, it is expected that you are familiar with <strong>Zenoss</strong> from a configuration perspective.1.1.2 Development EnvironmentThe process of developing a ZenPack can be made much faster and less frustrating by starting with a good developmentenvironment. The following development environment setup is only a set of recommendations. If you already havetools or techniques that you’re more comfortable with or you consider superior, you should use <strong>the</strong>m.2


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseInstalling <strong>Zenoss</strong>To best develop and deploy ZenPacks into a production environment, I recommend having three <strong>Zenoss</strong> systems:development, staging and production.Development <strong>Zenoss</strong> SystemYour development <strong>Zenoss</strong> system should not be shared with o<strong>the</strong>rs, and should be as minimal an installation as possible.This will allow for problems to be diagnosed, fixed and tested as quickly as possible.I recommend installing <strong>Zenoss</strong> from <strong>the</strong> single zenoss-x.y.z RPM on Red Hat Enterprise Linux or CentOS. The<strong>Zenoss</strong> installation instructions will ordinarily have you install a zenoss-core-zenpacks RPM, and a zenoss- enterprisezenpacksRPM for commercial <strong>Zenoss</strong> customers. To keep <strong>the</strong> system as small as possible, <strong>the</strong>se should not be installedinto your ZenPack development system.Staging <strong>Zenoss</strong> SystemThe staging <strong>Zenoss</strong> system should be shared by all ZenPack developers. After a ZenPack has been successfully testedin <strong>the</strong> development system should it be installed into <strong>the</strong> staging environment.This system should mimic <strong>the</strong> production system as closely as possible. It should be installed in <strong>the</strong> same way as <strong>the</strong>production system, and should have <strong>the</strong> same base set of ZenPacks installed. The purpose of <strong>the</strong> staging system is todo final integration testing before ZenPacks are installed into <strong>the</strong> production system.Production <strong>Zenoss</strong> SystemNew ZenPacks and updates to existing ZenPacks should only be deployed into <strong>the</strong> production system after <strong>the</strong>y’vebeen successfully tested in <strong>the</strong> development and staging systems.Running a Minimal <strong>Zenoss</strong>Often times ZenPack development is done within virtual machines, or spare hardware that doesn’t have <strong>the</strong> sameresources as a production <strong>Zenoss</strong> system. Additionally, during development you will need to restart <strong>Zenoss</strong> far morefrequently than in a production setup. For <strong>the</strong>se reasons you will want to run as little of <strong>Zenoss</strong> as necessary.After installing your <strong>Zenoss</strong> development system, run <strong>the</strong> following commands to reduce your <strong>Zenoss</strong> deployment to<strong>the</strong> minimum typical processes:su - zenosszenoss stopcat > $ZENHOME/etc/daemons.txt


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseNote: For <strong>the</strong> commercial version of <strong>Zenoss</strong> you will need to add zencatalogservice to <strong>the</strong> top of daemons.txt.See <strong>the</strong> following notes for more information on what <strong>the</strong>se commands are doing.1. The su - zenoss command is an important one that you’ll be using very frequently. This command switchesfrom <strong>the</strong> root user to <strong>the</strong> zenoss user. The hyphen (-) creates what’s called a login shell. This means that <strong>the</strong>zenoss user’s full environment will be loaded. This full environment is necessary to run any <strong>Zenoss</strong> commands.2. The zenoss stop command stops all <strong>Zenoss</strong> processes.3. The cat > ... that ends with EOF on a blank line writes those specific daemon names into <strong>the</strong>$ZENHOME/etc/daemons.txt file. This file contains <strong>the</strong> names of any daemons in addition to <strong>the</strong> defaultthat should be started, stopped and o<strong>the</strong>rwise managed by <strong>the</strong> zenoss master control script.4. The touch $ZENHOME/etc/DAEMONS_TXT_ONLY command will cause <strong>the</strong> zenoss master controlscript to only manage daemons listed in <strong>the</strong> aforementioned daemons.txt file.5. The zenoss start command will only start daemons listed in daemons.txt.Running <strong>Zenoss</strong> in <strong>the</strong> ForegroundTo take your development and debugging environment to <strong>the</strong> next level, I recommend running most <strong>Zenoss</strong> processesin <strong>the</strong> foreground with full debug logging enabled. This allows you to easily see exactly what is happening, andprovides a very quick way, CTRL-C, to stop and start individual processes. Perhaps <strong>the</strong> most useful benefit is that youcan use <strong>the</strong> Python debugger to set breakpoints without having <strong>the</strong>m hang background daemons.The zeneventserver process is <strong>the</strong> only process that I recommend running as a daemon while developing Zen-Packs. This is because <strong>the</strong>re are no changes you can make while developing a ZenPack that require restartingzeneventserver. There’s also no common reason you’d want to see its debugging output.You can open multiple SSH sessions to your <strong>Zenoss</strong> server, or use <strong>the</strong> GNU screen or tmux terminal multiplexers tosimultaneously run <strong>Zenoss</strong> processes in <strong>the</strong> foreground.Open five (5) sessions as <strong>the</strong> zenoss user on your zenoss server using any of <strong>the</strong> aforementioned methods. Thesessions should run <strong>the</strong> following commands respectively:1. zopectl fg2. zeneventd run -v103. zenhub run -v10 --workers=04. zenjobs run -v10 --cycle5. nothing: miscellaneous shell work is done hereThe -v10 option enables DEBUG level logging for <strong>the</strong> process. The --workers=0 option to zenhub is importantto prevent zenhub from spawning o<strong>the</strong>r worker process to do work. If work was performed in a worker process youmay not have foreground visibility to it. The --cycle option to zenjobs is necessary to prevent it from executing allcurrent pending jobs <strong>the</strong>n exiting.Installing ZenPacksZenPacks can be installed ei<strong>the</strong>r from a packaged .egg file, or from a source directory. The only situations in which Irecommend installing from a packaged egg is for <strong>the</strong> ZenPacks that ship with <strong>Zenoss</strong> and are automatically installedfrom <strong>the</strong>ir .egg file, and when <strong>the</strong> source is not available.There are some important reasons why installing in development mode from a source directory is preferable. Theyinclude:1.1. ZenPack Development Guide 4


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release• The running ZenPack code can be a checkout from version control. This makes it easier to audit ZenPack codefor changes.• ZenPacks can be upgraded in-place. Depending on <strong>the</strong> changes, this can often allow for less <strong>Zenoss</strong> daemonsneeding to be restarted after upgrading a ZenPack.When a new ZenPack is created in <strong>the</strong> user interface, it is created in development mode with <strong>the</strong> source directorylocated in $ZENHOME/ZenPacks/. To install or upgrade an existing ZenPack from it’s source directory, <strong>the</strong> --linkoption is used as follows:zenpack --link --install $ZENHOME/ZenPacks/ZenPacks.namespace.ZenPackName1.1.3 Background InformationThe section contains high-level background concepts and o<strong>the</strong>r information that will make <strong>the</strong> subsequent sectionseasier to understand. I recommend that you read and understand everything here.Device vs. DeviceComponentUnderstanding <strong>the</strong> difference between a <strong>Zenoss</strong> Device and DeviceComponent (often referred to as simply component)is useful for <strong>the</strong> <strong>Zenoss</strong> user, but critical to <strong>the</strong> ZenPack developer. The <strong>Zenoss</strong> object model has <strong>the</strong>se two primarytypes of objects that can be modeled and monitored.Device Devices are what you see on <strong>the</strong> <strong>Zenoss</strong> Infrastructure view in <strong>the</strong> web interface. If you see it in <strong>the</strong> Infrastructureview, it’s a device. If you don’t, it’s not. A device has an id attribute that makes it unique in <strong>the</strong> system.It will have configuration properties associated with it ei<strong>the</strong>r directly, or acquired from <strong>the</strong> device class withinwhich it is contained. It will also have a manageIp attribute that <strong>Zenoss</strong> uses for modeling and monitoring.Devices are contained within a single device class such as /Server/Linux or /Network/Ping. Devices and deviceclasses have a lot in common. They both have configuration properties (a.k.a zProperties) and can containmonitoring templates.Configuration properties and monitoring templates are defined in a hierarchical fashion with <strong>the</strong> most specificcopy being used. Take <strong>the</strong> following example of <strong>the</strong> zCommandUsername configuration property.Where zCommandUsername is set:• / = “”• /Server = “root”• /Server/Linux/AWS = “ec2-user”• server1 in /Server/Linux/AWS device class = “joeuser”In this case, <strong>the</strong> zCommandUsername used for server1 would be “joeuser” because it is most specific to server1.If zCommandUsername has not been set directly on <strong>the</strong> device, “ec2-user” would be used instead.Monitoring templates work <strong>the</strong> same way. If a device has <strong>the</strong> Device monitoring template bound to it, it willfirst check to see if <strong>the</strong>re’s a local copy of that monitoring template contained within <strong>the</strong> device. If <strong>the</strong>re isn’t,<strong>the</strong> device class path will be walked back to / until a monitoring template named Device is found.DeviceComponent Device components are what you see if you drill-down into a device in <strong>the</strong> web interface, <strong>the</strong>nchoose one of <strong>the</strong> component types from <strong>the</strong> left navigation pane. Each row in <strong>the</strong> grid in <strong>the</strong> top-right pane is acomponent. Examples include things like network interface, file systems, disks and processes. These are thingsthat have many instances per device.Device components, commonly just called “components”, don’t have <strong>the</strong>ir own configuration properties. Theyonly acquire configuration properties through <strong>the</strong> device that contains <strong>the</strong>m. They do not have a manageIpbecause <strong>the</strong>y’re typically managed through <strong>the</strong> same IP address and protocol(s) as <strong>the</strong> device that contains <strong>the</strong>m.1.1. ZenPack Development Guide 5


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseTemplate BindingHow monitoring templates get bound is different for devices and device classes than it is for components.A device or device class can easily have many monitoring templates bound to it. This is controlled by selecting BindTemplates from <strong>the</strong> gear menu of a device or device class. You will be presented with an available list of templates thatcould be bound on <strong>the</strong> left, and <strong>the</strong> list of templates that are currently bound on <strong>the</strong> right. You will only see monitoringtemplates that are appropriate to be bound directly to devices.Note: Using <strong>the</strong> Bind Templates dialog is really just setting <strong>the</strong> zDeviceTemplates configuration property in a morefriendly way. You can directly modify zDeviceTemplates to achieve <strong>the</strong> same result.Relationship TypesThe <strong>Zenoss</strong> model uses relationships between objects in <strong>the</strong> database in much <strong>the</strong> same way that a relational databasedoes. Here are examples of some of <strong>the</strong> relationships working behind <strong>the</strong> scenes in a standard <strong>Zenoss</strong> installation.• DeviceClass has many Device• Device has one OperatingSystem• OperatingSystem has one ProductClass• OperatingSystem has many FileSystem• OperatingSystem has many IpInterface• IpInterface has many IpAddress• IpNetwork has many IpAddressZenPacks can contribute new types of objects and new relationships to <strong>the</strong> system.Packs.zenoss.RabbitMQ adds <strong>the</strong> following object types and relationships.• Device has many RabbitMQNode• RabbitMQNode has many RabitMQVHost• RabbitMQVHost has many RabbitMQExchange• RabbitMQVHost has many RabbitMQueueThere are some important details you must understand to create new relationships.• There are containing and non-containing relationships.For example, Zen-The idea of containment comes from <strong>Zenoss</strong>’ database being a tree of objects. Every object in <strong>the</strong> database mustbe attached in some way to ano<strong>the</strong>r persistent object, and only one o<strong>the</strong>r persistent object.In <strong>the</strong> case of a containing relationship you’re saying that this is <strong>the</strong> relationship that attaches <strong>the</strong> member objectsto <strong>the</strong> object model and <strong>the</strong>refore is how <strong>the</strong>y get persisted. Removing an object from its containing relationshipis <strong>the</strong> same thing as deleting <strong>the</strong> object from <strong>the</strong> database entirely. Any non-containing relationships it mighthave will be cleaned up.Of <strong>the</strong> above example standard relationships, <strong>the</strong> following are implemented as containing relationships.– DeviceClass has many Device– Device has one OperatingSystem– OperatingSystem has many FileSystem– OperatingSystem has many IpInterface1.1. ZenPack Development Guide 6


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release– IpNetwork has many IpAddressOn <strong>the</strong> o<strong>the</strong>r hand, a non-containing relationship has nothing to do with persistence. Non-containing relationshipsare used to create logical linkages from one object to ano<strong>the</strong>r.Of <strong>the</strong> above example standard relationships, <strong>the</strong> following are implemented as non-containing relationships.– OperatingSystem has one ProductClass– IpInterface has many IpAddress• All relationships must be bi-directional and explicitly defined on both sides of <strong>the</strong> relationship.In <strong>the</strong> RabbitMQNode has many RabitMQVHost example above this means that RabbitMQNode must declarethat it has a ToManyCont relationship to RabbitMQVHost, and RabbitMQVHost must declare that it has a ToOnerelationship to RabbitMQNode.Relationship ClassesRelationships between object types (Python classes) are objects <strong>the</strong>mselves. You use <strong>the</strong> following classes from <strong>the</strong>Products.ZenRelations.RelSchema module to define <strong>the</strong>m.• ToMany• ToManyCont• ToOne• ToOneContOnly <strong>the</strong> following pairs of relationships are valid because of <strong>the</strong> requirement that relationships be defined bidirectionally.• ToMany ToOne (non-containing many-to-one)• ToMany ToMany (non-containing many-to-many)• ToManyCont ToOne (containing many-to-one)• ToOneCont ToOne (containing one-to-one)1.1.4 Creating a ZenPackThe actual creation of a new ZenPack is very simple, and done through <strong>the</strong> <strong>Zenoss</strong> web interface. However, <strong>the</strong>re someconsiderations related to <strong>the</strong> ZenPack’s name and version that should be considered at creation time. Once a ZenPackis in use by you and potentially o<strong>the</strong>rs renaming or changing <strong>the</strong> version scheme can be more painful.Follow <strong>the</strong>se steps to create a new ZenPack:1. Login to <strong>the</strong> <strong>Zenoss</strong> web interface as a user with <strong>the</strong> global Manager role.2. Navigate to <strong>the</strong> Advanced section.3. Choose ZenPacks from <strong>the</strong> left navigation.4. Choose Create a ZenPack from <strong>the</strong> gear menu next to Loaded ZenPacks.5. Name <strong>the</strong> ZenPack according to <strong>the</strong> Naming a ZenPack documentation.6. Click OK.7. Set Version according to <strong>the</strong> ZenPack Versioning documentation.8. Set Author to Your Name .1.1. ZenPack Development Guide 7


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release9. Set Dependencies according to <strong>the</strong> ZenPack Dependencies documentation.10. Click Save.After setting <strong>the</strong> version, author and dependencies for your ZenPack you should export it so <strong>the</strong> changes get written to<strong>the</strong> ZenPack’s source on <strong>the</strong> file system.1. Choose Export ZenPack from <strong>the</strong> gear menu in <strong>the</strong> bottom-left corner of <strong>the</strong> screen from <strong>the</strong> ZenPack’s detailpage.2. Leave Export to $ZENHOME/exports selected <strong>the</strong>n click OK.Naming a ZenPackZenPacks exist within <strong>the</strong> ZenPacks namespace. <strong>Zenoss</strong> requires at least one additional level of namespace before<strong>the</strong> name of <strong>the</strong> ZenPack to guard against different <strong>Zenoss</strong> developers creating ZenPacks by <strong>the</strong> same name. See <strong>the</strong>following ZenPack name as an example:ZenPacks.zenoss.Memcached• ZenPacks is mandatory.• zenoss is your definable namespace. zenoss is reserved for <strong>Zenoss</strong>, Inc. If you’re creating a ZenPack that youaim to publish as open source into <strong>the</strong> <strong>Zenoss</strong> community as <strong>the</strong> standard ZenPack for performing some function,<strong>the</strong> community namespace is recommended. O<strong>the</strong>rwise your company name or username should be used.• Memcached is <strong>the</strong> name of <strong>the</strong> ZenPack and should describe what <strong>the</strong> ZenPack is for in <strong>the</strong> shortest way possible.The most common functionality to provide with a ZenPack is to monitoring something. For this reason a namelike Memcached is acceptable over MemcachedMonitor.It’s also possible to use additional namespaces. It will mainly just create more typing for <strong>the</strong> developer, so it isn’t oftenused. An example of why you might want to do this is if you want to create many ZenPacks around <strong>the</strong> same targetsystem. A fictitious example of this would be AWS (Amazon Web Services) monitoring.• ZenPacks.community.AWS.EC2• ZenPacks.community.AWS.S3• ZenPacks.community.AWS.RDSZenPack VersioningZenPacks should be thought of as standalone software packages and versioned as such. A three part version numberis recommended. For example, X.Y.Z (major.*minor*.*patch*).Follow <strong>the</strong>se simple rules to maintain a proper version of your ZenPack:• 0.7.0: First version you’re playing with that o<strong>the</strong>rs should still be very frightened of. (a.k.a alpha)• 0.9.0: First version that you feel is consumable by o<strong>the</strong>rs. (a.k.a. beta)• 1.0.0: First stable version that has been thoroughly tested. Ideally by people o<strong>the</strong>r than <strong>the</strong> original developer,and in environments o<strong>the</strong>r than that of <strong>the</strong> original author.For versions prior to 1.0.0, any bug fixes, enhancements or API breakages should result in <strong>the</strong> patch number beingincremented.For versions subsequent to 1.0.0, <strong>the</strong> following rules should be followed:• Bug fixes result in <strong>the</strong> patch number being incremented.• New features that are backward-compatible result in <strong>the</strong> minor number being incremented and <strong>the</strong> patch numberbeing reset to 0 even if bug fixes are also included.1.1. ZenPack Development Guide 8


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release• New features are not backward-compatible result in <strong>the</strong> major number being incremented and <strong>the</strong> minor andpatch numbers being reset to 0 even if backwards-compatible features or bug fixes are also included.I recommend Semantic Versioning for a reference on good software versioning practices.ZenPack DependenciesYou can specify dependencies for your ZenPack. There are two major dependency types. Dependency on <strong>the</strong> supportedversion of <strong>the</strong> <strong>Zenoss</strong> platform, and dependencies on o<strong>the</strong>r ZenPacks.For ZenPacks installed in developer mode, you can view and edit dependencies by going to Advanced -> ZenPacks ->Your ZenPack in <strong>the</strong> web interface. Under <strong>the</strong> Dependencies section you’ll have a row for <strong>Zenoss</strong> and a row for everyo<strong>the</strong>r ZenPack that’s installed in <strong>the</strong> system.If you don’t plan to test your ZenPack on prior versions of <strong>Zenoss</strong>, I recommend always setting <strong>the</strong> Versions(s) field for<strong>the</strong> <strong>Zenoss</strong> row to be >=CURRENT_ZENOSS_MINOR_VERSION where CURRENT_ZENOSS_MINOR_VERSIONwould be something like 3.2 or 4.2. This will prevent your ZenPack from being installed on earlier versions of <strong>Zenoss</strong>.If your ZenPack depends on functionality provided by o<strong>the</strong>r ZenPacks, you can choose to simply put a check mark in<strong>the</strong> Required field for each of those ZenPacks and not specify version. This only enforces that some version of thatZenPack must be installed. To create a version-specific dependency, you would enter a >=VERSION or ==VERSIONin <strong>the</strong> Versions(s) field for each ZenPack dependency.For more information on what comparisons can be used in <strong>the</strong> Version(s) field you can reference <strong>the</strong> Python RequirementsParsing documentation.1.1.5 Monitoring an SNMP-Enabled DeviceThe following sections will describe a common approach to monitoring an SNMP- enabled device. We’ll start with<strong>the</strong> basics that can be done without writing a line of code, and <strong>the</strong>n move on to more sophisticated capabilities.For purposes of this guide we’ll be building a ZenPack to support a NetBotz environmental sensor device. This devicehas a variety of sensors that monitor temperature, humidity, dew point, audio levels and air flow.Exercises:SNMP ToolsTo configure <strong>Zenoss</strong> to monitor a device using SNMP, it is necessary to understand a bit about SNMP and <strong>the</strong> specificcapabilities of your device. This section will walk you through using Net-SNMP tools to learn about SNMP and yourdevice.The netsnmp-utils package is a prerequisite to installing <strong>Zenoss</strong> so you already have <strong>the</strong> SNMP tools you needinstalled on your <strong>Zenoss</strong> server.Using SNMPosterWhen developing a ZenPack to monitor an SNMP-enabled device it can often be useful to simulate <strong>the</strong> device’s SNMPagent. There are many tools out <strong>the</strong>re that can be used to do this. Some commercial and some free. Out of <strong>the</strong> freetools I recommend SNMPoster mainly because it can easily be run on your <strong>Zenoss</strong> development system, and usessnmpwalk output as its input format. This makes it easy to grab and use data from real devices.Use <strong>the</strong> following instructions to setup SNMPoster to simulate <strong>the</strong> NetBotz device used throughout this guide. Thesesteps should all be run as <strong>the</strong> root user.1. Install SNMPoster according to <strong>the</strong> instructions on its site.1.1. ZenPack Development Guide 9


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release2. Download and configure <strong>the</strong> NetBotz agent.mkdir -p /etc/snmposter/agentscd /etc/snmposter/agentswget --no-check-certificate https://github.com/clu<strong>the</strong>r/snmposter/raw/master/agents/NetBotz.snmpwcat > /etc/snmposter/agents.csv


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release.1.3.6.1.2.1.1.4.0 = STRING: unknown.1.3.6.1.2.1.1.5.0 = STRING: Netbotz01.1.3.6.1.2.1.1.6.0 = STRING: Z1 Rack02 NetBotz01While this data is mostly less valuable than <strong>the</strong> decoded version above, it’s more useful for a single reason. We cantake that .1.3.6.1.4.1.5528.100.20.10.2006 value and search <strong>the</strong> Internet for it. It’s best to remove <strong>the</strong>leading . and search for 1.3.6.1.4.1.5528.100.20.10.2006 instead.This should lead you to <strong>the</strong> NETBOTZV2-MIB which will contain <strong>the</strong> decoding information we need to learn moreabout this device. Download NETBOTZV2-MIB.mib and copy it into <strong>the</strong> /usr/share/snmp/mibs/ directoryof your <strong>Zenoss</strong> server.Now we can run <strong>the</strong> original snmpwalk command again with <strong>the</strong> addition of <strong>the</strong> -m all option. This option tellsNet-SNMP tools to use all MIBs:# snmpwalk -v2c -c public -m all 127.0.1.113 systemSNMPv2-MIB::sysDescr.0 = STRING: Linux Netbotz01 2.4.26 #1 Wed Oct 31 18:09:53 CDT 2007 ppcSNMPv2-MIB::sysObjectID.0 = OID: NETBOTZV2-MIB::netBotz420ERackDISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (7275488) 20:12:34.88SNMPv2-MIB::sysContact.0 = STRING: unknownSNMPv2-MIB::sysName.0 = STRING: Netbotz01SNMPv2-MIB::sysLocation.0 = STRING: Z1 Rack02 NetBotz01Now we can see that <strong>the</strong> sysObjectID is NETBOTZV2-MIB::netBotz420ERack. This gives us a better idea of exactlywhat kind of device it is. We’ll see that as we look deeper into this device that <strong>the</strong> NETBOTZV2-MIB will prove moreuseful.Default Net-SNMP OptionsThe snmpwalk usage showed three primary command line options that we tend to use most of <strong>the</strong> time. Net-SNMPallows you to specify <strong>the</strong>se in a configuration file so you don’t have to type <strong>the</strong>m every time. I recommend doing this.Create /etc/snmp/snmp.conf and add <strong>the</strong> following lines:defVersion v2cdefCommunity publicmibs ALLThese lines add <strong>the</strong> following equivalent command line options respectively:• -v2c• -c public• -m allSo now we can run this command:snmpwalk 127.0.1.113And get <strong>the</strong> same results as if we ran:snmpwalk -v2c -c public -m all 127.0.1.113Trust me that this will save you time while developing this ZenPack, and o<strong>the</strong>rs in <strong>the</strong> future.1.1. ZenPack Development Guide 11


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseDecoding and Encoding OIDsOften it can be useful to turn numeric OIDs into <strong>the</strong>ir human-readable equivalent, or vice-versa.snmptranslate command can be used for this. See <strong>the</strong> following examples.OID to name:The# snmptranslate .1.3.6.1.4.1.5528.100.20.10.2006NETBOTZV2-MIB::netBotz420ERackName to OID:# snmptranslate -On NETBOTZV2-MIB::netBotz420ERack.1.3.6.1.4.1.5528.100.20.10.2006Device MonitoringThis section will cover monitoring device-level metrics using SNMP. This requires no code, and you can find instructionsfor doing it in <strong>the</strong> normal <strong>Zenoss</strong> documentation. However, <strong>the</strong>re are some extra considerations and stepsrequired to package your configuration in a ZenPack.Create a Device ClassTo support our new NetBotz environmental sensor device we will want to create a new device class. This will give usfull control over how <strong>the</strong>se types of devices are modeled and monitored. Use <strong>the</strong> following steps to add a new deviceclass.1. Navigate to <strong>the</strong> Infrastructure view.2. Select <strong>the</strong> root of <strong>the</strong> DEVICES tree.3. Click <strong>the</strong> + button at <strong>the</strong> bottom of <strong>the</strong> list to add a new organizer.4. Set <strong>the</strong> Name to NetBotz <strong>the</strong>n click SUBMIT.The new NetBotz device will now be selected. We’ll want to check on some important configuration propertiesusing <strong>the</strong> following steps.Set Device Class Properties1. Click <strong>the</strong> DETAILS button at <strong>the</strong> top of <strong>the</strong> list.2. Select Modeler Plugins.The modeler plugins are what model information about <strong>the</strong> device. We should see a list something like <strong>the</strong>following. This list is being acquired from <strong>the</strong> root (/ or /Devices) device class.• zenoss.snmp.NewDeviceMap• zenoss.snmp.DeviceMap• zenoss.snmp.InterfaceMap• zenoss.snmp.RouteMapThis is a good basic list that uses standard MIB-2 support that works with most SNMP-enabled devices. However,it’s unlikely that we care about <strong>the</strong> routing table on our environmental sensors, so <strong>the</strong>re’s no reason tomodel it.3. Remove zenoss.snmp.RouteMap from <strong>the</strong> list.1.1. ZenPack Development Guide 12


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release4. Click Save.Now you can see <strong>the</strong> Path at which our modeler plugin configuration is set has changed from / to /NetBotz.This allows us to know that regardless of what <strong>the</strong> user sets <strong>the</strong>ir default modeler plugins to in <strong>the</strong> system thatNetBotz appliances will be collected using <strong>the</strong> set of modeler plugins we configure here.5. Select Configuration Properties from <strong>the</strong> left navigation pane.There are a lot of configuration properties. You don’t have to worry about understanding all of <strong>the</strong>m. However,some will be critical to monitoring NetBotz appliances. We know that we’re going to be using SNMP so let’smake sure that it’s enabled.6. Find <strong>the</strong> zSnmpMonitorIgnore property and set its value to true.7. Now set <strong>the</strong> value for zSnmpMonitorIgnore to false.The reason for flipping <strong>the</strong> value back to it’s original value is <strong>the</strong> same as saving <strong>the</strong> list of modeler plugins.While <strong>the</strong> system default is to have SNMP monitoring enabled, a user could easily disable it globally and causeour NetBotz monitoring to stop working. By flipping <strong>the</strong> value, we’ve set it locally within our device class andwill prevent changes in <strong>the</strong> global default from affecting <strong>the</strong> operation of our ZenPack.Add Device Class to ZenPack Now that we’ve setup <strong>the</strong> NetBotz device class, it’s time to add it to our ZenPackusing <strong>the</strong> following steps. Adding a device class to your ZenPack causes all settings in that device class to be added to<strong>the</strong> ZenPack. This includes modeler plugin configuration, configuration property values and monitoring templates.1. Make sure you’ve already created <strong>the</strong> ZenPack.2. Make sure that you have <strong>the</strong> NetBotz device class selected in <strong>the</strong> Infrastructure view.3. Choose Add to ZenPack from <strong>the</strong> gear menu in <strong>the</strong> bottom-left.4. Select your NetBotz ZenPack <strong>the</strong>n click SUBMIT.Add a NetBotz Device This would be a great time to add a NetBotz device to our new /NetBotz device class. Wehaven’t done anything in <strong>the</strong> way of customer monitoring. It can often be helpful to see what <strong>Zenoss</strong>’ default settingswill return for a device before we start adding features.You can add a <strong>the</strong> device through <strong>the</strong> web interface, or on <strong>the</strong> command line using zendisc as follows:zendisc run --deviceclass=/NetBotz --device=127.0.1.113Note: I’ll often use zendisc from <strong>the</strong> command line only because <strong>the</strong> zenjobs daemon must be running to add jobsfrom <strong>the</strong> web interface. The zenjobs daemon is not required to be running when adding devices using zendisc from <strong>the</strong>command line because it immediately adds <strong>the</strong> device instead of scheduling a job to do it.You should now see that <strong>Zenoss</strong> was able to model some information about <strong>the</strong> device even though we haven’t addedany custom monitoring. For example, you should see <strong>the</strong> following on <strong>the</strong> device in <strong>the</strong> web interface.• Overview– Hardware Manufacturer: NetBotz– Hardware Model: .1.3.6.1.4.1.5528.100.20.10.2006– OS Manufacturer: Unknown– OS Model: Linux 2.4.26• Components - Interfaces: 2 - eth0 and loIf we were running <strong>the</strong> zenperfsnmp daemon, we’d start to see that <strong>Zenoss</strong> was monitoring <strong>the</strong> uptime and interfacemetrics after about 10 minutes.1.1. ZenPack Development Guide 13


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseConfigure Monitoring TemplatesBefore adding a monitoring template we should look to see what monitoring templates are already being used in ournew device class.Validate Existing Monitoring Templates We created <strong>the</strong> NetBotz device class directly within <strong>the</strong> root (or /) deviceclass. This means that we’ll be inheriting <strong>the</strong> system default monitoring templates and binding. Use <strong>the</strong> followingsteps to validate this.1. Select <strong>the</strong> NetBotz device class in <strong>the</strong> Infrastructure view.2. Choose Bind Templates from <strong>the</strong> gear menu in <strong>the</strong> bottom-left.You should only see Device (/Devices) in <strong>the</strong> Selected box. Depending on what o<strong>the</strong>r ZenPacks youhave installed in <strong>the</strong> system you may see zero or more o<strong>the</strong>r templates listed in <strong>the</strong> Available box.Now we investigate what this system default Device monitoring template does.3. Click CANCEL on <strong>the</strong> Bind Templates dialog.4. Click <strong>the</strong> DETAILS button at <strong>the</strong> top of <strong>the</strong> device class tree.5. Select Device (/Devices) under Monitoring Templates.You’ll see that <strong>the</strong>re’s a single SNMP datasource named sysUpTime. If you expand this datasource you will seethat it contains a single datapoint which is also named sysUpTime. This single datapoint named <strong>the</strong> same asits containing datasource is always what you’ll see for SNMP datasources. The reason for having <strong>the</strong> conceptualseparation between datasources and datapoints is that o<strong>the</strong>r types of datasources such as COMMAND arecapable of returning multiple datapoints.You’ll note that this monitoring template has no threshold or graphs defined. This is unusual. Typically <strong>the</strong>re’dbe no reason to collect data that you weren’t going to ei<strong>the</strong>r threshold against or show in a graph. The sysUpTimedatapoint is an exception because it is shown on a device’s Overview page in <strong>the</strong> Uptime field and <strong>the</strong>reforedoesn’t need to be graphed.Let’s use snmpwalk to check if our NetBotz device supports sysUpTime. The OID listed for <strong>the</strong> sysUpTime datasourceis 1.3.6.1.2.1.1.3.0 so we run <strong>the</strong> following command:# snmpwalk 127.0.1.113 1.3.6.1.2.1.1.3.0DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (7275488) 20:12:34.88This response indicates that <strong>the</strong> NetBotz device does support <strong>the</strong> sysUpTime OID. This is a mandatory field for SNMPdevices to support so you will be able to get it in almost all cases.Add a Monitoring Template Now that we’ve validated that <strong>the</strong> existing Device monitoring template will work onour NetBotz device, we’ll add ano<strong>the</strong>r monitoring template to collect additional information.Note: We could create a local copy of <strong>the</strong> Device monitoring template in <strong>the</strong> NetBotz device class and add newdatasources, thresholds and graphs to it. However, this prevents us from taking advantage of changes made to <strong>the</strong>system default Device template in <strong>the</strong> future.Follow <strong>the</strong>se steps to create and bind a new template to <strong>the</strong> NetBotz device class.1. Navigate to Advanced -> Monitoring Templates.2. Click <strong>the</strong> + button in <strong>the</strong> bottom-left to add a template.1. Set <strong>the</strong> Name field to NetBotzDevice.2. Set <strong>the</strong> Template Path field to /NetBotz.1.1. ZenPack Development Guide 14


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release3. Click SUBMIT.4. Bind this template to <strong>the</strong> NetBotz device class.1. Navigate to Infrastructure.2. Select <strong>the</strong> NetBotz device class.3. Choose Bind Templates from <strong>the</strong> gear menu in <strong>the</strong> bottom-left.4. Move NetBotzDevice from available to selected.5. Click SAVE.Build <strong>the</strong> Monitoring Template Now that we’ve created <strong>the</strong> NetBotzDevice monitoring template and bound it to<strong>the</strong> NetBotz device class, we need to add datasources, thresholds and graphs. We don’t already know what might beinteresting to graph for each NetBotz device, so let’s go exploring with snmpwalk:# snmpwalk 127.0.1.113 .1.3SNMPv2-MIB::sysDescr.0 = STRING: Linux Netbotz01 2.4.26 #1 Wed Oct 31 18:09:53 CDT 2007 ppcSNMPv2-MIB::sysObjectID.0 = OID: NETBOTZV2-MIB::netBotz420ERack... lots of lines removed ...SNMPv2-MIB::snmpInTotalReqVars.0 = Counter32: 4406... and more removed ...There isn’t much of interest to collect at <strong>the</strong> device level. By “device-level” I mean values that only have a singleinstance for <strong>the</strong> device. Typical examples of <strong>the</strong>se kinds of metrics would be memory utilization or <strong>the</strong> previoussysUpTime example. With SNMP it can be easy to find <strong>the</strong>se kinds of single-instance values because <strong>the</strong>ir OID endsin .0 as in SNMPv2-MIB::snmpInTotalReqVars.0.Note: We’ll get into monitoring multi-instance values in <strong>the</strong> component monitoring section.Since <strong>the</strong>re aren’t any extremely interesting single-instance values to collect, we’ll collect that snmpInTotalReqVarsfor illustrative purposes. We’ll need to know <strong>the</strong> numeric OID for this value. Use snmptranslate to find it:# snmptranslate -On SNMPv2-MIB::snmpInTotalReqVars.0.1.3.6.1.2.1.11.13.0Add an SNMP Datasource1. Navigate to Advanced -> Monitoring Templates.2. Expand NetBotzDevice <strong>the</strong>n select /NetBotz.3. Click + on <strong>the</strong> Data Sources pane.1. Set Name to snmpInTotalReqVars2. Set Type to SNMP3. Click SUBMIT.Use <strong>the</strong> steps below to add an SNMP datasource for snmpInTotalReqVars.Note: Best practice is to name SNMP datasources according to <strong>the</strong> name of <strong>the</strong> OID that’s being polledfrom <strong>the</strong> MIB.4. Double-click to edit <strong>the</strong> snmpInTotalReqVars datasource.1. Set OID to 1.3.6.1.2.1.11.13.01.1. ZenPack Development Guide 15


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release2. Click SAVE.Warning: A common mistake to make when setting <strong>the</strong> OID in a device-level template is to omit <strong>the</strong>trailing .0. The reason this is common is that if you were using <strong>the</strong> MIB as reference instead of <strong>the</strong> snmpwalkabove, you’d see that <strong>the</strong> OID for SNMPv2-MIB::snmpInTotalReqVars was 1.3.6.1.2.1.11.13instead of 1.3.6.1.2.1.11.13.0. Due to this, I always recommend using snmpwalk to verify exactlywhat OID you should be polling.While <strong>Zenoss</strong> will accept <strong>the</strong> OID with <strong>the</strong> leading ., I recommend omitting it. It isn’t necessary.We now have a choice about how we want to handle <strong>the</strong> value that comes back from polling that OID. As you can seeabove in <strong>the</strong> snmpwalk output, it is a Counter32 type. This means that it starts at 0 and, in this case, increments eachtime an SNMP variable is requested. The most common way to handle counters like <strong>the</strong>se is as a delta. It’s not veryinteresting to know how many variables have been requested since <strong>the</strong> device last rebooted, but it might be interestingto know how many variables are requested per second.The default type for a datapoint is GAUGE which would record <strong>the</strong> actual value you see in <strong>the</strong> snmpwalk output. Ifwe’d ra<strong>the</strong>r monitor <strong>the</strong> rate of requests, we’d change <strong>the</strong> datapoint type to DERIVE using <strong>the</strong> following steps.1. Double-click on <strong>the</strong> snmpInTotalReqVars.snmpInTotalReqVars datapoint.You may need to expand <strong>the</strong> snmpInTotalReqVars datasource first.1. Set RRD Type to DERIVE2. Set RRD Minimum to 03. Click SAVE.Warning: It is very important to always set <strong>the</strong> RRD Minimum to 0 for DERIVE type datapoints. If you fail to dothis, you will get large negative spikes in your data anytime <strong>the</strong> device reboots or <strong>the</strong> counter resets for any o<strong>the</strong>rreason.The only time you wouldn’t set a minimum of 0 is when <strong>the</strong> value you’re monitoring can increase and decreaseand you’re interested in tracking rates of negative change as well as rates of positive change.Add a Threshold Now we can add a threshold to our monitoring template. Let’s say we want to raise a warningevent anytime <strong>the</strong> rate of SNMP variable requests exceeds 10 per second. This can be done with <strong>the</strong> following steps.1. Click + on <strong>the</strong> Thresholds pane.1. Set Name to high SNMP variable request rate2. Set Type to MinMaxThreshold3. Click ADD.2. Double-click to edit <strong>the</strong> high SNMP variable request rate threshold.1. Drag <strong>the</strong> snmpInTotalReqVars datapoint to <strong>the</strong> left box.2. Set Severity to Warning3. Set Maximum Value to 104. Set Event Class to /Perf/Snmp5. Click SAVE.Note: A MinMaxThreshold can be used to handle a variety of conditions including over a maximum value, under aminimum value, outside a defined range or within a defined range. See <strong>the</strong> regular <strong>Zenoss</strong> documentation for how touse each of <strong>the</strong>se options.1.1. ZenPack Development Guide 16


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseAdd a Graph Definition Now we’ll add a graph so <strong>the</strong> user will be able to see <strong>the</strong> trend of SNMP variable requestsper second over time. This can be done with <strong>the</strong> following steps.1. Click + on <strong>the</strong> Graph Definitions pane.1. Set Name to SNMP Rates2. Click SUBMIT.2. Double-click to edit <strong>the</strong> SNMP Rates graph definition.1. Set Units to requests/sec2. Set Min Y to 03. Click SUBMIT.Note: Always set <strong>the</strong> units for your graph. Also set <strong>the</strong> minimum Y axis and maximum Y axis values ifyou know what <strong>the</strong> possible limits are for <strong>the</strong> data. This results in graphs that are easier to read.The format field should also be tweaked to best present <strong>the</strong> kind of data that is to be graphed. You can findmore information on what can be used in <strong>the</strong> format field in <strong>the</strong> RRDtool rrdgraph_graph documentationunder <strong>the</strong> PRINT section.3. Select <strong>the</strong> SNMP Rates graph definition.4. Choose Manage Graph Points from <strong>the</strong> gear menu.1. Choose Data Point from <strong>the</strong> + menu.2. Set Data Point to snmpInTotalReqVars3. Check Include Related Thresholds4. Click SUBMIT5. Double-click to edit <strong>the</strong> snmpInTotalReqVars graph point.1. Set Name to Variables2. Click SAVE.Note: The name of a graph point is what is displayed for it in <strong>the</strong> graph legend. You should alwayschoose something short that describes <strong>the</strong> data and makes sense in context of <strong>the</strong> units chosen above.You can find many more notes about how to create monitoring templates along with best practices on graph styling in<strong>the</strong> ZenPack Standards Guide.Test Monitoring TemplateThe quick way to check if we’ve been successful in creating and binding our monitoring template is to navigate to<strong>the</strong> NetBotz device we added to <strong>the</strong> system and verify that we see our NetBotzDevice (/NetBotz) monitoring templatelisted at <strong>the</strong> bottom of <strong>the</strong> device’s left navigation pane.Now we can test that our datasource will be collected by running <strong>the</strong> following command to do a single collection of<strong>the</strong> NetBotz device:zenperfsnmp run -v10 --device=Netbotz01We can look through <strong>the</strong> output to see what zenperfsnmp does. Personally I look for any lines that contain zen.RRDUtil.These lines will show <strong>the</strong> collected data being written to RRD files. If data isn’t collected, <strong>the</strong>se lines won’t be present.Because of this you might run <strong>the</strong> following command instead to only see lines that contain this pattern:1.1. ZenPack Development Guide 17


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Releasezenperfsnmp run -v10 --device=Netbotz01 2>&1 | grep "zen.RRDUtil"We should see about 16 datapoints being written into RRD files. You’ll see sysUpTime, 14 interface datapoints andour custom snmpInTotalReqVars in <strong>the</strong>re somewhere.Device ModelingThis section will cover creation of a custom Device subclass and modeling of device attributes.For purposes of this example, we’ll add a temp_sensor_count attribute to NetBotz devices. We’ll walk through adding<strong>the</strong> attribute to <strong>the</strong> model, modeling it from <strong>the</strong> device, and displaying it in <strong>the</strong> overview screen for NetBotz devices.Starting in this section we’ll be working with a lot of files within <strong>the</strong> NetBotz ZenPack’s directory. To keep <strong>the</strong> pathnames short, I’ll assume <strong>the</strong> $ZP_DIR_TOP and $ZP_DIR environment variables have been set as follows.export ZP_DIR_TOP=$ZENHOME/ZenPacks/ZenPacks.training.NetBotzexport ZP_DIR=$ZP_DIR_TOP/ZenPacks/training/NetBotzCreate a Device SubclassA Device subclass should not be confused with a device class. In <strong>the</strong> previous section we created <strong>the</strong> /NetBotz deviceclass from <strong>the</strong> web interface. Creating a Device subclass means to extend <strong>the</strong> actual Python class of a Device object.You’d do this to add new attributes, methods or relationships to special device types.Use <strong>the</strong> following steps to create a NetBotzDevice class with a new attribute called temp_sensor_count.1. Create $ZP_DIR/NetBotzDevice.py with <strong>the</strong> following contents.from Products.ZenModel.Device import Deviceclass NetBotzDevice(Device):temp_sensor_count = None_properties = Device._properties + ({’id’: ’temp_sensor_count’, ’type’: ’int’},)We’re importing <strong>the</strong> base Device class <strong>the</strong>n creating our own class called NetBotzDevice that extends it. We <strong>the</strong>nadd a default value of None for <strong>the</strong> new temp_sensor_count attribute. We <strong>the</strong>n add to our base class’ _propertiescollection to describe <strong>the</strong> type of data we’ll be putting into our temp_sensor_count attribute. This _propertiessystem comes from Zope.2. Restart your zopectl process so <strong>the</strong> web interface can load our new module.3. Change <strong>the</strong> zPythonClass property on <strong>the</strong> /NetBotz device class toZenPacks.training.NetBotz.NetBotzDeviceThe zPythonClass property controls tells <strong>Zenoss</strong> what class of object to instantiate when a new device isadded to <strong>the</strong> device class. The default value for this property is blank. If <strong>the</strong> value is blank or invalid, Products.ZenModel.Devicewill be used.Note: Those with Python experience might notice that ZenPacks.training.NetBotz.NetBotzDevice is a module,not a class. This is true. The terminology is wrong. One can only assume that <strong>the</strong> creator of this functionalitydidn’t appreciate <strong>the</strong> difference between Python modules and classes.1.1. ZenPack Development Guide 18


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release4. Reset <strong>the</strong> Python class of our existing device.Run zendmd and execute <strong>the</strong> following snippet.device = find("Netbotz01")print device.__class__You should see . We see this instead of <strong>the</strong> Python class we justcreated because <strong>the</strong> zPythonClass property is only used when a new device is created in a device class, or whena device is moved into a device class with a differing zPythonClass value.So we have two options for getting our NetBotz device to use <strong>the</strong> new Python class we created. We can ei<strong>the</strong>rdelete <strong>the</strong> device and add it back, or move it to a different device class and back. Actually, <strong>the</strong>re’s a third optionthat I use most frequently to solve this problem. I move it into <strong>the</strong> same device class using zendmd. Execute <strong>the</strong>following snippet within zendmd to reset <strong>the</strong> device’s Python class.dmd.Devices.NetBotz.moveDevices(’/NetBotz’, ’Netbotz01’)commit()device = find("Netbotz01")print device.__class__Now you should see printed. This confirms that our Devicesubclass works, and that we’ve configure zPythonClass correctly for <strong>the</strong> /NetBotz device class.Find Temperature Sensor CountBefore we can write a modeler plugin to populate our new temp_sensor_count attribute, we need to figure out how toget <strong>the</strong> information. There are a few ways we could approach this. One way is to use that NETBOTZV2-MIB as areference to see if we can find anything about temperature sensors specifically.<strong>Zenoss</strong> comes with a tool called smidump that makes finding information in MIBs much easier. There are a lot ofMIB browser tools out <strong>the</strong>re that make this even easier, but I primarily use a Mac and haven’t found very good options<strong>the</strong>re.Find temperature information in NETBOTZV2-MIB using <strong>the</strong> following command.smidump -f identifiers /usr/share/snmp/mibs/NETBOTZV2-MIB.mib | egrep -i tempYou should see <strong>the</strong> following in <strong>the</strong> output:NETBOTZV2-MIB tempSensorTable table 1.3.6.1.4.1.5528.100.4.1.1NETBOTZV2-MIB tempSensorEntry row 1.3.6.1.4.1.5528.100.4.1.1.1NETBOTZV2-MIB tempSensorId column 1.3.6.1.4.1.5528.100.4.1.1.1.1NETBOTZV2-MIB tempSensorValue column 1.3.6.1.4.1.5528.100.4.1.1.1.2NETBOTZV2-MIB tempSensorErrorStatus column 1.3.6.1.4.1.5528.100.4.1.1.1.3NETBOTZV2-MIB tempSensorLabel column 1.3.6.1.4.1.5528.100.4.1.1.1.4NETBOTZV2-MIB tempSensorEncId column 1.3.6.1.4.1.5528.100.4.1.1.1.5NETBOTZV2-MIB tempSensorPortId column 1.3.6.1.4.1.5528.100.4.1.1.1.6NETBOTZV2-MIB tempSensorValueStr column 1.3.6.1.4.1.5528.100.4.1.1.1.7NETBOTZV2-MIB tempSensorValueInt column 1.3.6.1.4.1.5528.100.4.1.1.1.8NETBOTZV2-MIB tempSensorValueIntF column 1.3.6.1.4.1.5528.100.4.1.1.1.9You’ll also see ano<strong>the</strong>r node and a bunch of notification entries. These are related to SNMP traps, and not relevant towhat we’re interested in polling right now.What we see here is that <strong>the</strong>re isn’t a single OID we can request that will tell us <strong>the</strong> number of temperature sensors.We’re going to have to do an snmpwalk of <strong>the</strong> table <strong>the</strong>n count how many rows are in <strong>the</strong> response. Specifically1.1. ZenPack Development Guide 19


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Releasewe want to remember <strong>the</strong> name and OID for <strong>the</strong> row: tempSensorEntry. Due to <strong>the</strong> hierarchical nature of a MIBsrepresentation this is <strong>the</strong> most specific OID that will return <strong>the</strong> data we need.snmpwalk 127.0.1.113 1.3.6.1.4.1.5528.100.4.1.1.1You’ll see a lot of output that starts with:NETBOTZV2-MIB::tempSensorId.21604919 = STRING: nbHawkEnc_1_TEMPNETBOTZV2-MIB::tempSensorId.1095346743 = STRING: nbHawkEnc_0_TEMPNETBOTZV2-MIB::tempSensorId.1382714817 = STRING: nbHawkEnc_2_TEMP1NETBOTZV2-MIB::tempSensorId.1382714818 = STRING: nbHawkEnc_2_TEMP2NETBOTZV2-MIB::tempSensorId.1382714819 = STRING: nbHawkEnc_2_TEMP3NETBOTZV2-MIB::tempSensorId.1382714820 = STRING: nbHawkEnc_2_TEMP4NETBOTZV2-MIB::tempSensorId.1382714833 = STRING: nbHawkEnc_3_TEMP1NETBOTZV2-MIB::tempSensorId.1382714834 = STRING: nbHawkEnc_3_TEMP2NETBOTZV2-MIB::tempSensorId.1382714865 = STRING: nbHawkEnc_1_TEMP1NETBOTZV2-MIB::tempSensorId.1382714866 = STRING: nbHawkEnc_1_TEMP2NETBOTZV2-MIB::tempSensorId.1382714867 = STRING: nbHawkEnc_1_TEMP3NETBOTZV2-MIB::tempSensorId.1382714868 = STRING: nbHawkEnc_1_TEMP4NETBOTZV2-MIB::tempSensorId.2169088567 = STRING: nbHawkEnc_3_TEMPNETBOTZV2-MIB::tempSensorId.3242830391 = STRING: nbHawkEnc_2_TEMPWhat you’re seeing above is <strong>the</strong> tempSensorId column for all 14 rows in <strong>the</strong> tempSensorTable. Continuing on you willsee 14 rows for each of <strong>the</strong> o<strong>the</strong>r columns in <strong>the</strong> table.Create a Modeler PluginThe next step is to build a modeler plugin. A modeler plugin’s responsibility reach out into <strong>the</strong> world, ga<strong>the</strong>rdata, and plug it into <strong>the</strong> attributes and relationships of our model classes. In this example, this means to make<strong>the</strong> SNMP requests necessary to determine how many temperature sensors a NetBotz device has, and populate ourtemp_sensor_count attribute with <strong>the</strong> result.Use <strong>the</strong> following steps to create our modeler plugin.1. Make <strong>the</strong> directory that’ll contain our modeler plugin.mkdir -p $ZP_DIR/modeler/plugins/training/snmpNote that we’re using our ZenPack’s training namespace, <strong>the</strong>n snmp. This is <strong>the</strong> recommended approach tomake it clear what protocol <strong>the</strong> modeler plugin will use, and to avoid our modeler plugin conflicting with onefrom someone else’s ZenPack.2. Create __init__.py or dunder-init files.touch $ZP_DIR/modeler/__init__.pytouch $ZP_DIR/modeler/plugins/__init__.pytouch $ZP_DIR/modeler/plugins/training/__init__.pytouch $ZP_DIR/modeler/plugins/training/snmp/__init__.pyThese empty __init__.py files are mandatory if we ever expect Python to import modules from <strong>the</strong>se directories.3. Create $ZP_DIR/modeler/plugins/training/snmp/NetBotz.py with <strong>the</strong> following contents.from Products.DataCollector.plugins.CollectorPlugin import (SnmpPlugin, GetTableMap,)class NetBotz(SnmpPlugin):1.1. ZenPack Development Guide 20


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleasesnmpGetTableMaps = (GetTableMap(’tempSensorTable’, ’1.3.6.1.4.1.5528.100.4.1.1.1’, {’.1’: ’tempSensorId’,}),)def process(self, device, results, log):temp_sensors = results[1].get(’tempSensorTable’, {})return self.objectMap({’temp_sensor_count’: len(temp_sensors.keys()),})(a) Start by importing SnmpPlugin and GetTableMap from <strong>Zenoss</strong>. SnmpPlugin will handle all of <strong>the</strong> SNMPrequests for us and present <strong>the</strong> results in a format we can easily work with. GetTableMap will be used herebecause we need to request an SNMP table ra<strong>the</strong>r than specific OIDs.(b) Our NetBotz class extends SnmpPlugin. Note that <strong>the</strong> NetBotz class name must match <strong>the</strong> filename (modulename) of <strong>the</strong> modeler plugin.(c) By defining snmpGetTableMaps as a tuple or list on our class we can add a GetTableMap object that requeststhat 1.3.6.1.4.1.5528.100.4.1.1.1 row OID and specify that we only want to get <strong>the</strong> first (.1) columnand name it tempSensorId.(d) The process method will receive a two-element tuple containing <strong>the</strong> SNMP request results in <strong>the</strong> requestparameter. The first elememt, results[0], of this tuple would be any direct OID gets of which we didn’trequest any in this plugin. The second element, results[1] will contain a dictionary of <strong>the</strong> table results. Inthis case results[1] would look like <strong>the</strong> following.{}’tempSensorTable’: {’21604919’: ’nbHawkEnc_1_TEMP’,’1095346743’: ’nbHawkEnc_0_TEMP’,’1382714817’: ’nbHawkEnc_2_TEMP1’,’1382714818’: ’nbHawkEnc_2_TEMP2’,’1382714819’: ’nbHawkEnc_2_TEMP3’,’1382714820’: ’nbHawkEnc_2_TEMP4’,’1382714833’: ’nbHawkEnc_3_TEMP1’,’1382714834’: ’nbHawkEnc_3_TEMP2’,’1382714865’: ’nbHawkEnc_1_TEMP1’,’1382714866’: ’nbHawkEnc_1_TEMP2’,’1382714867’: ’nbHawkEnc_1_TEMP3’,’1382714868’: ’nbHawkEnc_1_TEMP4’,’2169088567’: ’nbHawkEnc_3_TEMP’,’3242830391’: ’nbHawkEnc_2_TEMP’,},(e) We <strong>the</strong>n extract just <strong>the</strong> tempSensorTable results into temp_sensors to make <strong>the</strong> next return line a bit easierto understand.(f) We <strong>the</strong>n return a dictionary that sets <strong>the</strong> temp_sensor_count key’s value to <strong>the</strong> number of keys intemp_sensors. Actually we return a dictionary that’s been wrapped in an ObjectMap by <strong>the</strong> modelerplugin’s objectMap utility method.The process method within all modeler plugins must return one of <strong>the</strong> following types of data.• None (makes no changes to <strong>the</strong> model)1.1. ZenPack Development Guide 21


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release• ObjectMap (to apply directly to <strong>the</strong> device that’s being modeled)• RelationshipMap (to apply to a relationship within <strong>the</strong> device)• A list containing zero or more ObjectMap and/or RelationShipMap objects.An ObjectMap is simply a dict wrapped with some meta-data. A RelationshipMap is a list wrapped withsome meta-data and containing zero or more ObjectMap instances.4. Restart zopectl and zenhub to load <strong>the</strong> new module.5. Add our new training.snmp.NetBotz modeler plugin to <strong>the</strong> list of modeler plugins for <strong>the</strong> /NetBotz device class.Test <strong>the</strong> Modeler Plugin1. Remodel <strong>the</strong> NetBotz device.Now that we’ve created and enabled a basic modeler plugin, we should test it.You can do this from <strong>the</strong> web interface, but I usually use <strong>the</strong> command line because it can be easier to work withif fur<strong>the</strong>r debugging is necessary.zenmodeler run --device=Netbotz012. Execute <strong>the</strong> following snippet in zendmd.device = find("Netbotz01")print device.temp_sensor_countYou should see 14 printed as <strong>the</strong> number of temperature sensors.Create <strong>the</strong> APIThe <strong>Zenoss</strong> web interface is a consumer of <strong>the</strong> <strong>Zenoss</strong> JSON API. This is now relevant to you because you have tomake sure that you extend <strong>the</strong> API to allow <strong>the</strong> web interface to know about <strong>the</strong> new class of object you’ve created.Now that you’re creating custom classes, you’ll need to instruct <strong>Zenoss</strong> how your Python objects should be translatedwhen <strong>the</strong> web interface or o<strong>the</strong>r API user requests information about <strong>the</strong>m. This is a three part process that involvescreating an IInfo interface for your class, an Info adapter, and finally registering <strong>the</strong>m for use.Create <strong>the</strong> IInfo InterfaceThis is where we define <strong>the</strong> public interface for <strong>the</strong> NetBotzDevice class we created.1. Create $ZP_DIR/interfaces.py with <strong>the</strong> following contents.from Products.Zuul.form import schemafrom Products.Zuul.interfaces.device import IDeviceInfofrom Products.Zuul.utils import ZuulMessageFactory as _tclass INetBotzDeviceInfo(IDeviceInfo):temp_sensor_count = schema.Int(title=_t(’Number of Temperature Sensors’))(a) Start by importing schema. This is how we specify <strong>the</strong> types of <strong>the</strong> attributes.(b) We <strong>the</strong>n import IDeviceInfo. This is <strong>the</strong> Info interface for standard devices. By extending it, we get <strong>the</strong>standard device API for free.(c) ZuulMessageFactory as _t allows any strings we wrap in _t() to have translations to o<strong>the</strong>r languagesprovided for <strong>the</strong>m.(d) Next we create our Interface class, INetBotzDeviceInfo. Note that it’s our model class name, NetBotzDevice,prefixed with I and suffixed with Info. This is a best practice that can make it easier to figure outwhat’s going on for people later.1.1. ZenPack Development Guide 22


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release(e) Finally we define our single new attribute, temp_sensor_count. It’s a number with no decimal precision sowe use Int.Create <strong>the</strong> Info Adapter Now that we’ve defined <strong>the</strong> Info interface we have to provide <strong>the</strong> implementation for it.This is what will be returned when <strong>the</strong> web interface asks for <strong>the</strong> Info for one of our NetBotzDevice objects.1. Create $ZP_DIR/info.py with <strong>the</strong> following contents.from zope.interface import implementsfrom Products.Zuul.infos import ProxyPropertyfrom Products.Zuul.infos.device import DeviceInfofrom ZenPacks.training.NetBotz.interfaces import INetBotzDeviceInfoclass NetBotzDeviceInfo(DeviceInfo):implements(INetBotzDeviceInfo)temp_sensor_count = ProxyProperty(’temp_sensor_count’)(a) Import <strong>the</strong> symbols we’ll need to implement our Info adapter.(b) Just like with <strong>the</strong> interface, we can extend DeviceInfo to get all of <strong>the</strong> standard Device functionality forfree. Note that we’re again using a best practice naming convention for our Info adapter. It should be <strong>the</strong>name of <strong>the</strong> model class suffixed with Info.(c) The implements line tells <strong>the</strong> system that this is an implementation of <strong>the</strong> interface we previously defined.(d) We <strong>the</strong>n use <strong>the</strong> helpful ProxyProperty method to provide <strong>the</strong> temp_sensor_count directly from <strong>the</strong> attributeby <strong>the</strong> same name on <strong>the</strong> actual model object.Using ProxyProperty in this way is equivalent to <strong>the</strong> following.@propertydef temp_sensor_count(self):return self._adapted.temp_sensor_count@temp_sensor_count.setterdef temp_sensor_count(self, value):self._adapted.temp_sensor_count = valueAs you can see, ProxyProperty is shorter and cleaner even if you were only interested in <strong>the</strong> getter.Register <strong>the</strong> Info Adapter Now that you’ve defined your API interface and implemented <strong>the</strong> adapter to <strong>the</strong> modelclass you have to register <strong>the</strong>m with <strong>the</strong> system. O<strong>the</strong>rwise <strong>the</strong>y won’t be used, and <strong>the</strong> system will use <strong>the</strong> standardIDeviceInfo interface and DeviceInfo adapter because <strong>the</strong>y’re <strong>the</strong> next best thing.Follow <strong>the</strong>se steps to register your API interface and adapter.1. Create $ZP_DIR/configure.zcml with <strong>the</strong> following contents.1.1. ZenPack Development Guide 23


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release(a) We open with a standard XML header and declaring that <strong>the</strong> default XML namespace (xmlns) for <strong>the</strong>document will be Zope’s main namespace.(b) Registering <strong>the</strong> Info adapter is <strong>the</strong> only thing we need in here at this point. We must specify <strong>the</strong> interface<strong>the</strong> adapter provides, <strong>the</strong> type of object that it provides <strong>the</strong> interface for, and finally <strong>the</strong> adapter factoryitself. This is boilerplate stuff that you’ll see in a lot of o<strong>the</strong>r areas in <strong>Zenoss</strong> and ZenPack development.Test <strong>the</strong> API We can now test <strong>the</strong> API using zendmd. Be sure to restart zendmd after making changes to interfaces.py,info.py, or configure.zcml.1. Execute <strong>the</strong> following snippet in zendmd.from Products.Zuul.interfaces import IInfodevice = find("Netbotz01")device_info = IInfo(device)print device_info.temp_sensor_count(a) Calling IInfo(device) will return <strong>the</strong> best IInfo adapter for device which is a NetBotzDevice instancein this case.You should see 14 printed if everything worked.Change <strong>the</strong> Device OverviewWe’ve come a long way, but aside from going into zendmd to test that <strong>the</strong> API works, we don’t have much to show forit. The next step will be to show <strong>the</strong> number of temperature sensors to users of <strong>the</strong> web interface. We’ll replace <strong>the</strong>Memory/Swap field in <strong>the</strong> top-left box of <strong>the</strong> device overview page with <strong>the</strong> count of temperature sensors.Follow <strong>the</strong>se steps to customize <strong>the</strong> device Overview page.1. Create a directory to store our ZenPack’s JavaScript.mkdir -p $ZP_DIR/browser/resources/js2. Create __init__.py or dunder-init files.touch $ZP_DIR/browser/__init__.py3. Create $ZP_DIR/browser/resources/js/NetBotzDevice.js with <strong>the</strong> following contents.Ext.on<strong>Read</strong>y(function() {var DEVICE_OVERVIEW_ID = ’deviceoverviewpanel_summary’;Ext.ComponentMgr.onAvailable(DEVICE_OVERVIEW_ID, function(){var overview = Ext.getCmp(DEVICE_OVERVIEW_ID);overview.removeField(’memory’);});});overview.addField({name: ’temp_sensor_count’,fieldLabel: _t(’# Temperature Sensors’)});(a) Wait for Ext to be ready.1.1. ZenPack Development Guide 24


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release(b) Find <strong>the</strong> overview summary panel (top-left on Overview page)(c) Remove <strong>the</strong> memory field.(d) Add our temp_sensor_count field.<strong>Zenoss</strong> uses ExtJS as its JavaScript framework. You can find more in ExtJS’s documentation about manipulatingobjects in this way.4. Create $ZP_DIR/browser/configure.zcml with <strong>the</strong> following contents.(a) We open with a standard XML header and declaring that <strong>the</strong> default XML namespace (xmlns) for <strong>the</strong>document will be Zope’s browser namespace.(b) Next we register <strong>the</strong> resourceDirectory. This allows files within your ZenPack’s /browser/resources/ directoryto be served from URLs such as http://zenoss.example.com/++resource++netbotz/filename.js.(c) Next we use a viewlet register a JavaScript snippet. You can see that we’re referencing a URL within <strong>the</strong>resourceDirectory and limiting <strong>the</strong> snippet to only appear on pages where <strong>the</strong> context is a NetBotzDevice.This is <strong>the</strong> important part that keeps our customizations local to NetBotz devices.5. Edit $ZP_DIR/configure.zcml. Add <strong>the</strong> following section before <strong>the</strong> closing .This makes <strong>Zenoss</strong> load our browser/configure.zcml on startup.Test <strong>the</strong> Device Overview That’s it. We can restart zopectl and navigate to our NetBotz device’s overview page in<strong>the</strong> web interface. You should see # Temperature Sensors label with a value of 14 at <strong>the</strong> bottom of <strong>the</strong> top-leftpanel.Component ModelingThis section will cover creation of a custom DeviceComponent subclass, creation of a relationship to our NetBotDeviceclass, and modeling of <strong>the</strong> components to fill <strong>the</strong> relationship.In <strong>the</strong> Device Modeling section we added a temp_sensor_count attribute to our NetBotz devices. This isn’t very useful.It would be more useful to monitor <strong>the</strong> temperature being reported by each of <strong>the</strong>se sensors. So that’s what we’ll do.1.1. ZenPack Development Guide 25


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseModeling each sensor as a component allows <strong>Zenoss</strong> to automatically discover and monitor sensors regardless of howmany a particular device has.Find Temperature Sensor AttributesIn <strong>the</strong> Device Modeling section we used smidump to extract temperature sensor information from NETBOTZV2-MIB.This will be even more applicable as we decide what attributes and metrics are available on each sensor. Let’s usesmidump and snmpwalk for a refresher on what’s available.Find temperature information in NETBOTZV2-MIB using <strong>the</strong> following command.smidump -f identifiers /usr/share/snmp/mibs/NETBOTZV2-MIB.mib | egrep -i tempYou should see <strong>the</strong> following in <strong>the</strong> output:NETBOTZV2-MIB tempSensorTable table 1.3.6.1.4.1.5528.100.4.1.1NETBOTZV2-MIB tempSensorEntry row 1.3.6.1.4.1.5528.100.4.1.1.1NETBOTZV2-MIB tempSensorId column 1.3.6.1.4.1.5528.100.4.1.1.1.1NETBOTZV2-MIB tempSensorValue column 1.3.6.1.4.1.5528.100.4.1.1.1.2NETBOTZV2-MIB tempSensorErrorStatus column 1.3.6.1.4.1.5528.100.4.1.1.1.3NETBOTZV2-MIB tempSensorLabel column 1.3.6.1.4.1.5528.100.4.1.1.1.4NETBOTZV2-MIB tempSensorEncId column 1.3.6.1.4.1.5528.100.4.1.1.1.5NETBOTZV2-MIB tempSensorPortId column 1.3.6.1.4.1.5528.100.4.1.1.1.6NETBOTZV2-MIB tempSensorValueStr column 1.3.6.1.4.1.5528.100.4.1.1.1.7NETBOTZV2-MIB tempSensorValueInt column 1.3.6.1.4.1.5528.100.4.1.1.1.8NETBOTZV2-MIB tempSensorValueIntF column 1.3.6.1.4.1.5528.100.4.1.1.1.9Let’s now use snmpwalk to see what <strong>the</strong>se values look like on our NetBotz device.snmpwalk 127.0.1.113 1.3.6.1.4.1.5528.100.4.1.1.1You should see a lot of output that begins with <strong>the</strong> following:NETBOTZV2-MIB::tempSensorId.21604919 = STRING: nbHawkEnc_1_TEMPNETBOTZV2-MIB::tempSensorId.1095346743 = STRING: nbHawkEnc_0_TEMPNETBOTZV2-MIB::tempSensorId.1382714817 = STRING: nbHawkEnc_2_TEMP1NETBOTZV2-MIB::tempSensorId.1382714818 = STRING: nbHawkEnc_2_TEMP2Note <strong>the</strong> 21604919 in <strong>the</strong> first response. This is <strong>the</strong> SNMP index of <strong>the</strong> first temperature sensor, or <strong>the</strong> first row in <strong>the</strong>table. I like to <strong>the</strong>n restrict my snmpwalk results to only show this row with a command like <strong>the</strong> following.snmpwalk 127.0.1.113 1.3.6.1.4.1.5528.100.4.1.1.1 | grep "\.21604919 ="Which will show us <strong>the</strong> value of each column for that one temperature sensor:NETBOTZV2-MIB::tempSensorId.21604919 = STRING: nbHawkEnc_1_TEMPNETBOTZV2-MIB::tempSensorValue.21604919 = INTEGER: 265NETBOTZV2-MIB::tempSensorErrorStatus.21604919 = INTEGER: normal(0)NETBOTZV2-MIB::tempSensorLabel.21604919 = STRING: TemperatureNETBOTZV2-MIB::tempSensorEncId.21604919 = STRING: nbHawkEnc_1NETBOTZV2-MIB::tempSensorPortId.21604919 = STRING:NETBOTZV2-MIB::tempSensorValueStr.21604919 = STRING: 26.500000NETBOTZV2-MIB::tempSensorValueInt.21604919 = INTEGER: 26NETBOTZV2-MIB::tempSensorValueIntF.21604919 = INTEGER: 79Now we have everything we should need to make decisions about what attributes we should model for our sensors andwhich would better be collected as datasources to have thresholds applied and plotted over time on graphs.My initial thoughts would be to model <strong>the</strong> following as attributes.1.1. ZenPack Development Guide 26


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release• tempSensorId• tempSensorEncId (enclosure ID)• tempSensorPortIdI would <strong>the</strong>n want to collect tempSensorValueStr as a datasource because it offers <strong>the</strong> best precision. <strong>Zenoss</strong> is capableof handling numeric strings so we don’t have to collect tempSensorValue and divide it by 10 like o<strong>the</strong>r systems might.Create a DeviceComponent SubclassUse <strong>the</strong> following steps to create a TemperatureSensor class with <strong>the</strong> attributes discovered above.1. Create $ZP_DIR/TemperatureSensor.py with <strong>the</strong> following contents.from Products.ZenModel.DeviceComponent import DeviceComponentfrom Products.ZenModel.ManagedEntity import ManagedEntityfrom Products.ZenModel.<strong>Zenoss</strong>Security import ZEN_CHANGE_DEVICEfrom Products.ZenRelations.RelSchema import ToManyCont, ToOneclass TemperatureSensor(DeviceComponent, ManagedEntity):meta_type = portal_type = ’NetBotzTemperatureSensor’enclosure = Noneport = None_properties = ManagedEntity._properties + ({’id’: ’enclosure’, ’type’: ’string’},{’id’: ’port’, ’type’: ’string’},)_relations = ManagedEntity._relations + ((’sensor_device’, ToOne(ToManyCont,’ZenPacks.training.NetBotz.NetBotzDevice’,’temperature_sensors’,)),)factory_type_information = ({’actions’: ({’id’: ’perfConf’,’name’: ’Template’,’action’: ’objTemplates’,’permissions’: (ZEN_CHANGE_DEVICE,),},),},)def device(self):return self.sensor_device()def getRRDTemplateName(self):return ’TemperatureSensor’(a) Start by importing <strong>the</strong> symbols we’ll need.(b) Define <strong>the</strong> TemperatureSensor class. This name must match <strong>the</strong> filename.(c) Extend (inherit from) both <strong>the</strong> DeviceComponent and ManagedEntity classes.1.1. ZenPack Development Guide 27


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseDeviceComponent provides <strong>the</strong> core functionality that’s needed to associate our temperature sensor with adevice.ManagedEntity provides base functionality for objects that are to be monitored. This base class providesattributes such as snmpindex, monitor and productionState.(d) Next we set both <strong>the</strong> meta_type and portal_type of <strong>the</strong> class to NetBotzTemperatureSensor. Thisis used as <strong>the</strong> friendly name for <strong>the</strong> type of our object in various places in <strong>the</strong> web interface such as <strong>the</strong>global search. meta_type and portal_type should always be <strong>the</strong> same. They both exist for backwardscompatibility reasons.(e) Next we add <strong>the</strong> enclosure and port attributes in <strong>the</strong> same way as we did for our NetBotzDevice class.Note: Despite noting above that we always wanted to model <strong>the</strong> tempSensorId attribute, we aren’t addingan attribute for it here. This is because DeviceComponent already has both an id and title attribute thatwherein we can store <strong>the</strong> value of tempSensorId.(f) Next we have to setup _relations for our component type. This wasn’t mandatory for our NetBotzDeviceclass because devices automatically get a containing relationship in <strong>the</strong>ir device class. Component’s don’tautomatically get a containing relationship in <strong>the</strong>ir device because it can’t automatically be known whichone of <strong>the</strong>ir device’s relations <strong>the</strong>y belong in, or if <strong>the</strong>y’re perhaps nested under ano<strong>the</strong>r component.The single relationship we’re adding can be expressed in English as “TemperatureSensor has a relationnamed sensor_device that refers to a single object of <strong>the</strong> ZenPacks.training.NetBotz.NetBotzDevice type.”We also describe <strong>the</strong> o<strong>the</strong>r side of this relationship as “NetBotzDevice has a relation named temperature_sensorsthat contains zero or more objects of <strong>the</strong> TemperatureSensor type.”(g) The factory_type_information section is boilerplate that should be used for any component types to whichyou will bind monitoring templates.(h) We must define <strong>the</strong> device method for our component. Every component must provide this method. Wecan find our device by calling <strong>the</strong> sensor_device relation.(i) Finally we override <strong>the</strong> getRRDTemplateName method. This method allows us to control <strong>the</strong> name of <strong>the</strong>monitoring template that will be automatically bound to each temperature sensor.2. Edit $ZP_DIR/NetBotzDevice.py to add <strong>the</strong> following to <strong>the</strong> bottom._relations = Device._relations + ((’temperature_sensors’, ToManyCont(ToOne,’ZenPacks.training.NetBotz.TemperatureSensor’,’sensor_device’,)),)You’ll also need to add <strong>the</strong> following imports to <strong>the</strong> top of <strong>the</strong> file.from Products.ZenRelations.RelSchema import ToManyCont, ToOneIt is mandatory that this relationship definition exist, and be an exact mirror of <strong>the</strong> definition on <strong>the</strong> o<strong>the</strong>r side.Note: See <strong>the</strong> Relationship Types section for more information on relationships.Test TemperatureSensor Class With our component class defined and relationships setup we can use zendmd tomake sure we didn’t make any mistakes. Execute <strong>the</strong> following snippet in zendmd.1.1. ZenPack Development Guide 28


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Releasefrom ZenPacks.training.NetBotz.TemperatureSensor import TemperatureSensorsensor = TemperatureSensor(’test_sensor_01’)device = find("Netbotz01")device.temperature_sensors._setObject(sensor.id, sensor)sensor = device.temperature_sensors._getOb(sensor.id)print sensorprint sensor.device()You’ll most likely get <strong>the</strong> following error when executing <strong>the</strong> above snippet:Traceback (most recent call last):File "", line 1, in AttributeError: temperature_sensorsThis error is indicating that we have no temperature_sensors relationship on <strong>the</strong> device object. This would seeminglymake no sense because we just added it to NetBotzDevice.py above. The key here is that existing objects like <strong>the</strong>Netbotz01 device don’t automatically get new relationships. We have to ei<strong>the</strong>r delete <strong>the</strong> device and add it again, orexecute <strong>the</strong> following in zendmd to create <strong>the</strong> newly-defined relationship.device.buildRelations()commit()Now you can go back and run <strong>the</strong> original snippet again. You should see <strong>the</strong> name of <strong>the</strong> sensor and device objectsprinted if everything worked as planned.Update <strong>the</strong> Modeler PluginAs with <strong>the</strong> NetBotzDevice class, <strong>the</strong> next step after creating our model class is to populate it with a modeler plugin.We could create a new modeler plugin to only capture <strong>the</strong> temperature sensor components, but we’ll update <strong>the</strong> NetBotzmodeler plugin we previously created to model <strong>the</strong> sensors instead.1. Edit $ZP_DIR/modeler/plugins/training/snmp/NetBotz.py and replace its contents with <strong>the</strong>following.from Products.DataCollector.plugins.CollectorPlugin import (SnmpPlugin, GetTableMap,)class NetBotz(SnmpPlugin):relname = ’temperature_sensors’modname = ’ZenPacks.training.NetBotz.TemperatureSensor’snmpGetTableMaps = (GetTableMap(’tempSensorTable’, ’1.3.6.1.4.1.5528.100.4.1.1.1’, {’.1’: ’tempSensorId’,’.5’: ’tempSensorEncId’,’.6’: ’tempSensorPortId’,}),)def process(self, device, results, log):temp_sensors = results[1].get(’tempSensorTable’, {})1.1. ZenPack Development Guide 29


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Releaserm = self.relMap()for snmpindex, row in temp_sensors.items():name = row.get(’tempSensorId’)if not name:log.warn(’Skipping temperature sensor with no name’)continuerm.append(self.objectMap({’id’: self.prepId(name),’title’: name,’snmpindex’: snmpindex.strip(’.’),’enclosure’: row.get(’tempSensorEncId’),’port’: row.get(’tempSensorPortId’),}))return rmLet’s take a closer look at how we changed <strong>the</strong> modeler plugin.(a) We added relname and modname as class attributes.These two settings control <strong>the</strong> meta-data that will automatically be set when <strong>the</strong> self.relMap andself.objectMap methods are called in <strong>the</strong> process method.Setting relname to temperature_sensors will cause <strong>the</strong> self.relMap call to create a RelationshipMapthat will be applied to <strong>the</strong> temperature_sensors relationship defined on <strong>the</strong> NetBotzDevice object.Setting modname to ZenPacks.training.NetBotz.TemperatureSensor will cause <strong>the</strong>self.objectMap calls in <strong>the</strong> process method to create ObjectMap instances that will be turned into instancesof our TemperatureSensor class.(b) We’re now requesting <strong>the</strong> tempSensorEncId and tempSensorPortId columns be returned in <strong>the</strong> SNMP tablerequest results. We’ll use <strong>the</strong>se to populate <strong>the</strong>ir corresponding fields on <strong>the</strong> TemperatureSensor class.(c) Most of <strong>the</strong> process method has been changed.We’re now creating a RelationshipMap and appending an ObjectMap to it for each temperature sensor in<strong>the</strong> results. We use <strong>the</strong> self.relMap and self.objectMap utility methods to make this easier.2. Restart zopectl and zenhub to load <strong>the</strong> changed module.Test <strong>the</strong> Modeler Plugin We already added <strong>the</strong> training.snmp.NetBotz modeler plugin <strong>the</strong> <strong>the</strong> /NetBotz device classin an earlier exercise. So we only need to run zenmodeler to test <strong>the</strong> temperature sensor modeling updates.1. Run zenmodeler run --device=Netbotz01We should see Changes in configuration applied near <strong>the</strong> end of zenmodeler’s output. The changes referred toshould be 14 temperature sensor objects being created and added to <strong>the</strong> device’s temperature_sensors relationship.2. Execute <strong>the</strong> following snipped in zendmd.device = find("Netbotz01")pprint(device.temperature_sensors())You should see a list of all 14 temperature sensors printed. We can test in more depth by validating that all ofour modeled attributes were set properly.for sensor in device.temperature_sensors():print "%17s: %-17s %-11s %-11s %-11s" % (1.1. ZenPack Development Guide 30


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Releasesensor.id, sensor.title, sensor.snmpindex, sensor.enclosure,sensor.port)Create <strong>the</strong> APIWe must now create and test <strong>the</strong> API for our new TemperatureSensor class much like we did for <strong>the</strong> NetBotzDeviceAPI. There’s a slight difference in <strong>the</strong> base interfaces and classes that we use because TemperatureSensor is aDeviceComponent subclass instead of a Device subclass.1. Create <strong>the</strong> IInfo interface.To create <strong>the</strong> public interface for <strong>the</strong> TemperatureSensor class we add <strong>the</strong> following class to <strong>the</strong> end of$ZP_DIR/interfaces.py.class ITemperatureSensorInfo(IComponentInfo):enclosure = schema.TextLine(title=_t(’Sensor Enclosure ID’))port = schema.TextLine(title=_t(’Sensor Port ID’))We must also add an import for our base interface, IComponentInfo.from Products.Zuul.interfaces.component import IComponentInfoNote: The attributes you add to your IComponentInfo interface control <strong>the</strong> fields that will be displayed when<strong>the</strong> Details for a component are viewed in <strong>the</strong> web interface.2. Create <strong>the</strong> Info adapter.To create <strong>the</strong> Info adapter for <strong>the</strong> TemperatureSensor class we add <strong>the</strong> following class to <strong>the</strong> end of$ZP_DIR/info.py.class TemperatureSensorInfo(ComponentInfo):implements(ITemperatureSensorInfo)enclosure = ProxyProperty(’enclosure’)port = ProxyProperty(’port’)We must also add an import for our base class, ComponentInfo.from Products.Zuul.infos.component import ComponentInfoAdditionally, we need to add an import of ITemperatureSensorInfo from our own interfaces module. This willrequire an update to <strong>the</strong> existing line.from ZenPacks.training.NetBotz.interfaces import (INetBotzDeviceInfo,ITemperatureSensorInfo,)3. Register <strong>the</strong> Info adapter.To register <strong>the</strong> Info adapter for TemperatureSensor we add <strong>the</strong> following before <strong>the</strong> browser include in$ZP_DIR/configure.zcml.1.1. ZenPack Development Guide 31


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseTest <strong>the</strong> API We can now test <strong>the</strong> API using zendmd. Be sure to restart zendmd after making changes to interfaces.py,info.py, or configure.zcml.1. Execute <strong>the</strong> following snippet in zendmd.from Products.Zuul.interfaces import IInfodevice = find("Netbotz01")sensor = device.temperature_sensors._getOb(’nbHawkEnc_1_TEMP1’)sensor_info = IInfo(sensor)print "id: %s, enclosure: %s, port: %s" % (sensor_info.id, sensor_info.enclosure, sensor_info.port)You should see <strong>the</strong> following line printed:id: nbHawkEnc_1_TEMP1, enclosure: nbHawkEnc_1, port: nbHawkEnc_1_DIN1Add Component Display JavaScriptTypically when a new type of component like TemperatureSensor is added, you will want to add two JavaScriptelements to provide for a more attractive display in <strong>the</strong> web interface.1. registerName to change TemperatureSensor to Temperature Sensors in <strong>the</strong> device’s left navigation pane underComponents.Add <strong>the</strong> following to $ZP_DIR/browser/resources/js/NetBotzDevice.js.(function(){var ZC = Ext.ns(’<strong>Zenoss</strong>.component’);ZC.registerName(’NetBotzTemperatureSensor’,_t(’Temperature Sensor’),_t(’Temperature Sensors’));})();(a) The JavaScript code is wrapped in an anonymous function to keep it out of <strong>the</strong> global namespace. Ideallyall ZenPack JavaScript code should be wrapped in this way.The wrapping is done using <strong>the</strong> first and last lines of <strong>the</strong> JavaScript snippet above.(b) Next we get a handle to <strong>the</strong> <strong>Zenoss</strong>.component ExtJS namespace and store it in <strong>the</strong> ZA variable.(c) Finally we actually register <strong>the</strong> name. The parameters to registerName are:i. meta_type of <strong>the</strong> class we’re registering names for.ii. Singular form of <strong>the</strong> human-friendly name of <strong>the</strong> class.iii. Plural form of <strong>the</strong> human-friendly name of <strong>the</strong> class.2. Create a ComponentGridPanel.A custom ComponentGridPanel allows us to customize <strong>the</strong> grid that will display in <strong>the</strong> top-right panel when weselect Temperature Sensors from our device’s component tree. Typically we customize what columns we wantto appear, how we want those columns to appear, and how <strong>the</strong>y should be sorted.Add <strong>the</strong> following to $ZP_DIR/browser/resources/js/NetBotzDevice.js beneath <strong>the</strong>ZC.registerName but before <strong>the</strong> })(); that ends <strong>the</strong> anonymous function.1.1. ZenPack Development Guide 32


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseZC.NetBotzTemperatureSensorPanel = Ext.extend(ZC.ComponentGridPanel, {constructor: function(config) {config = Ext.applyIf(config||{}, {componentType: ’NetBotzTemperatureSensor’,autoExpandColumn: ’name’,sortInfo: {field: ’name’,direction: ’ASC’},fields: [{name: ’uid’},{name: ’name’},{name: ’status’},{name: ’severity’},{name: ’usesMonitorAttribute’},{name: ’monitor’},{name: ’monitored’},{name: ’locking’},{name: ’enclosure’},{name: ’port’}],columns: [{id: ’severity’,dataIndex: ’severity’,header: _t(’Events’),renderer: <strong>Zenoss</strong>.render.severity,sortable: true,width: 50},{id: ’name’,dataIndex: ’name’,header: _t(’Name’),sortable: true},{id: ’enclosure’,dataIndex: ’enclosure’,header: _t(’Enclosure ID’),sortable: true,width: 120},{id: ’port’,dataIndex: ’port’,header: _t(’Port ID’),sortable: true,width: 120},{id: ’monitored’,dataIndex: ’monitored’,header: _t(’Monitored’),renderer: <strong>Zenoss</strong>.render.checkbox,sortable: true,width: 70},{id: ’locking’,dataIndex: ’locking’,header: _t(’Locking’),renderer: <strong>Zenoss</strong>.render.locking_icons,width: 651.1. ZenPack Development Guide 33


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release});}]});}ZC.NetBotzTemperatureSensorPanel.superclass.constructor.call(this, config);Ext.reg(’NetBotzTemperatureSensorPanel’, ZC.NetBotzTemperatureSensorPanel);This is a length snippet of JavaScript. Let’s go through it section by section.(a) Define a new ExtJS class named NetBotzTemperatureSensorPanel.We define this class within <strong>the</strong> <strong>Zenoss</strong>.component namespace by using <strong>the</strong> ZC variable we created in <strong>the</strong>previous step. Just like we’ve been defining our Python classes by extending existing <strong>Zenoss</strong> Pythonclasses, we do <strong>the</strong> same with JavaScript classes within <strong>the</strong> ExtJS framework. In this case we’re extending<strong>the</strong> ComponentGridPanel class.(b) Create <strong>the</strong> NetBotzTemperatureSensorPanel constructor.Our constructor method only does two things. First we override <strong>the</strong> config variable. Then we close bycalling our superclass’ constructor. Our superclass is <strong>the</strong> ComponentGridPanel we extended.(c) Override config variable.This is where all of <strong>the</strong> interesting stuff happens, and where we’ll be making changes. Let’s look at eachof <strong>the</strong> fields we’re changing within config.i. componentTypeMust match <strong>the</strong> meta_type on our TemperatureSensor Python class.ii. autoExpandColumnOne of <strong>the</strong> following columns can be picked to automatically expand to use <strong>the</strong> remaining space within<strong>the</strong> user’s web browser. Typically this is set to <strong>the</strong> name field, but it can be useful to choose a differentfield if <strong>the</strong> name is a well-known length and ano<strong>the</strong>r field is more variable.iii. sortInfoControls which field and direction that <strong>the</strong> grid will be sorted by when it initially appears. This is anoptional field and defaults to sort by name in ascending order if not set. So in this example <strong>the</strong> sortInfofield could have been left out.iv. fieldsControls which fields will be requested from <strong>the</strong> TemperatureSensorInfo API adapter on <strong>the</strong> server todisplay a column. You can request any attribute that you made available on <strong>the</strong> Info adapter. This evenincludes fields that are not present in <strong>the</strong> IInfo interface.You must be sure to include fields needed to display all of <strong>the</strong> columns specified below. For consistencyI recommend having <strong>the</strong> following minimum set plus any that are specific to your componenttype.• uid• name• status• severity• usesMonitorAttribute1.1. ZenPack Development Guide 34


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release• monitor• monitored• lockingv. columnsControls <strong>the</strong> visual display of columns in <strong>the</strong> grid. Each column can specify <strong>the</strong> following fields.• idA unique identifier for <strong>the</strong> field. Typically this is set to <strong>the</strong> same value as dataIndex.• dataIndexReference to one of <strong>the</strong> items from fields above.• headerText that will appear in <strong>the</strong> column’s header.• rendererJavaScript function that will be used to render <strong>the</strong> column’s data. This is an optional field and willdefault to displaying <strong>the</strong> data’s natural string representation.You can find <strong>the</strong> standard renderer choices in <strong>the</strong> following file:$ZENHOME/Products/ZenUI3/browser/resources/js/zenoss/Renderers.js• sortableWhe<strong>the</strong>r or not <strong>the</strong> user can choose to sort <strong>the</strong> grid by this column.• widthThe width in pixels of <strong>the</strong> column. This is an optional field, but I highly recommend setting in onall columns except for <strong>the</strong> column that’s referenced in autoExpandColumn.Test <strong>the</strong> Component DisplayWe test our component display JavaScript by looking at it in <strong>the</strong> web interface.If you’re running zopectl in <strong>the</strong> foreground, it is not necessary to restart it after making changes to existing files withinour resourceDirectory. However, you will have to force your browser to do a full refresh to make sure your browsercache isn’t interfering. This can typically be done by holding <strong>the</strong> SHIFT key while clicking <strong>the</strong> refresh button or typing<strong>the</strong> refresh shortcut.I recommend having <strong>the</strong> browser’s JavaScript console open while testing so you don’t miss any errors.Component MonitoringThis section covers monitoring component metrics using SNMP. I assume that you’ve completed <strong>the</strong> ComponentModeling steps and now have temperature sensor components modeled for <strong>the</strong> NetBotz device. Currently <strong>the</strong>re willbe no graphs for <strong>the</strong>se temperature sensors.We will add collection, thresholding and graphing for <strong>the</strong> temperature monitored by each sensor.1.1. ZenPack Development Guide 35


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseFind <strong>the</strong> SNMP OIDIn <strong>the</strong> Component Modeling section we used smidump and snmpwalk to find which values would be useful to model,and which would be useful to monitor. We found tempSensorValue to be <strong>the</strong> best OID to use for monitoring a sensor’scurrent temperature.Let’s use snmpwalk again to see what tempSensorValue looks like for all of <strong>the</strong> sensors on our NetBotz device.snmpwalk 127.0.1.113 NETBOTZV2-MIB::tempSensorValueStrThis gives of <strong>the</strong> current temperature (in celsius) for each sensor:NETBOTZV2-MIB::tempSensorValueStr.21604919 = STRING: 26.500000NETBOTZV2-MIB::tempSensorValueStr.1095346743 = STRING: 27.000000NETBOTZV2-MIB::tempSensorValueStr.1382714817 = STRING: 22.100000NETBOTZV2-MIB::tempSensorValueStr.1382714818 = STRING: 21.100000NETBOTZV2-MIB::tempSensorValueStr.1382714819 = STRING: 19.600000NETBOTZV2-MIB::tempSensorValueStr.1382714820 = STRING: 19.900000NETBOTZV2-MIB::tempSensorValueStr.1382714833 = STRING: 20.500000NETBOTZV2-MIB::tempSensorValueStr.1382714834 = STRING: 20.100000NETBOTZV2-MIB::tempSensorValueStr.1382714865 = STRING: 19.700000NETBOTZV2-MIB::tempSensorValueStr.1382714866 = STRING: 20.500000NETBOTZV2-MIB::tempSensorValueStr.1382714867 = STRING: 20.100000NETBOTZV2-MIB::tempSensorValueStr.1382714868 = STRING: 20.000000NETBOTZV2-MIB::tempSensorValueStr.2169088567 = STRING: 26.600000NETBOTZV2-MIB::tempSensorValueStr.3242830391 = STRING: 27.400000As we go on to add a monitoring template below, we’ll need to know what OID to poll to collect this value. The keyto determining this for components with an snmpindex attribute like TemperatureSensor has is to find <strong>the</strong> OID for <strong>the</strong>values above and remove <strong>the</strong> SNMP index from <strong>the</strong> end of it. Let’s use snmptranslate to do this.snmptranslate -On NETBOTZV2-MIB::tempSensorValueStrThis results in <strong>the</strong> following output:.1.3.6.1.4.1.5528.100.4.1.1.1.7This OID (minus <strong>the</strong> leading .) is what we’ll need.Add a Monitoring TemplateIn <strong>the</strong> Component Modeling section we added a getRRDTemplateName method to our TemperatureSensor class. Wemade this method return TemperatureSensor. This means that each temperature sensor will have a template by thisname automatically bound to it.This makes life easy when adding a monitoring template to be used. All we have to do is create a monitoring templatenamed TemperatureSensor in <strong>the</strong> /NetBotz device class.1. Navigate to Advanced -> Monitoring Templates.2. Add a template.(a) Click <strong>the</strong> + in <strong>the</strong> bottom-left of <strong>the</strong> template list.(b) Set Name to TemperatureSensor(c) Set Template Path to /NetBotz(d) Click SUBMIT3. Add a data source.1.1. ZenPack Development Guide 36


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release(a) Click <strong>the</strong> + at <strong>the</strong> top of <strong>the</strong> Data Sources panel.(b) Set Name to tempSensorValueStr(c) Set Type to SNMP(d) Click SUBMIT(e) Double-click to edit <strong>the</strong> tempSensorValueStr data source.(f) Set OID to 1.3.6.1.4.1.5528.100.4.1.1.1.7(g) Click SAVE5. Add a threshold.(a) Click <strong>the</strong> + at <strong>the</strong> top of <strong>the</strong> Thresholds panel.(b) Set Name to high temperature(c) Set Type to MinMaxThreshold(d) Click ADD(e) Double-click to edit <strong>the</strong> high temperature threshold.(f) Move <strong>the</strong> datapoint to <strong>the</strong> list on <strong>the</strong> right.(g) Set Maximum Value to 32(h) Set Event Class to /Environ(i) Click SAVE6. Add a graph.(a) Click <strong>the</strong> + at <strong>the</strong> top of <strong>the</strong> Graph Definitions panel.(b) Set Name to Temperature(c) Click SUBMIT(d) Double-click to edit <strong>the</strong> Temperature graph.(e) Set Units to degrees c.(f) Click SUBMIT7. Add a graph point.(a) Click to select <strong>the</strong> Temperature graph.(b) Choose Manage Graph Points from <strong>the</strong> gear menu.(c) Choose Data Point from <strong>the</strong> + menu.(d) Choose tempSensorValueStr <strong>the</strong>n click SUBMIT(e) Double-click to edit <strong>the</strong> tempSensorValueStr graph point.(f) Set Name to Temperature(g) Set Format to %7.2lf(h) Click SAVE <strong>the</strong>n SAVE again.1.1. ZenPack Development Guide 37


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseTest Monitoring Template You can now refer back to <strong>the</strong> Test Monitoring Template section of Device Monitoringfor using zenperfsnmp to test <strong>the</strong> data point collection aspect of your monitoring template.You can verify that your monitoring template is getting bound to each temperature sensor properly by navigating to oneof <strong>the</strong> temperature sensors in <strong>the</strong> web interface and choosing Templates from it’s Display drop-down box. Fur<strong>the</strong>rmore,you can verify that your Temperature graph is shown when choosing Graphs from <strong>the</strong> temperature sensor’s Displaydrop-down.SNMP TrapsThis section covers how to handle SNMP traps.<strong>Zenoss</strong> will accept SNMP traps from your devices as soon as you configure those devices to send traps to your <strong>Zenoss</strong>server. The zentrap daemon will listen to <strong>the</strong> standard SNMP trap port of 162/udp and create an event for every trapthat it receives.However, without you giving <strong>Zenoss</strong> more information about <strong>the</strong> contents of those traps, <strong>the</strong> events will containnumeric OIDs and be nearly impossible for a human to decipher.Importing MIBsLet’s import <strong>the</strong> NETBOTZV2-MIB that we’ve been working with through <strong>the</strong>se examples.1. Copy <strong>the</strong> MIB to <strong>Zenoss</strong>’ site-specific MIB directory.cp /usr/share/snmp/mibs/NETBOTZV2-MIB.mib $ZENHOME/share/mibs/site/2. Import <strong>the</strong> MIB directory.zenmib runFrom which we should get <strong>the</strong> following output:Found 1 MIBs to import.Parsed 214 nodes and 256 notifications from NETBOTZV2-MIBLoaded MIB NETBOTZV2-MIB into <strong>the</strong> DMDLoaded 1 MIB file(s)3. Add <strong>the</strong> imported MIB to <strong>the</strong> NetBotz ZenPack.(a) Navigate to Advanced -> MIBs in <strong>the</strong> web interface.(b) Select NETBOTZV2-MIB.(c) Choose Add to ZenPack from <strong>the</strong> gear menu at <strong>the</strong> bottom of <strong>the</strong> list.(d) Choose <strong>the</strong> ZenPacks.training.NetBotz <strong>the</strong>n click SUBMIT.Simulating SNMP TrapsTo more easily configure and test <strong>Zenoss</strong>’ trap handling, it’s useful to know how to simulate SNMP traps. Thealternative is breaking your real devices in various ways and hoping to be able to get <strong>the</strong> device to send all of <strong>the</strong> trapsyou need. This isn’t always possible.Let’s start by picking an SNMP trap to simulate.1. Navigate to Advanced -> MIBs in <strong>the</strong> web interface.2. Choose NETBOTZV2-MIB from <strong>the</strong> list of MIBs.1.1. ZenPack Development Guide 38


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release3. Choose Traps from <strong>the</strong> drop-down box in <strong>the</strong> middle of <strong>the</strong> right panel.4. Choose netBotzTempTooHigh in <strong>the</strong> list of traps.We’ll now see information about this trap in <strong>the</strong> bottom-right panel. The first thing to note is <strong>the</strong> OID. This is all weneed to get started.Send a Simple Trap1. Make sure zentrap is running.Use <strong>the</strong> following steps to get your feet wet sending a basic trap.As detailed in Development Environment section, I prefer to only run <strong>the</strong> processes necessary for debugging. SoI would run zentrap in <strong>the</strong> foreground in a new terminal session with <strong>the</strong> following command.zentrap run -v10 --cycle2. Run <strong>the</strong> following snmptrap command.snmptrap localhost 0 NETBOTZV2-MIB::netBotzTempTooHighIf you were running zentrap in <strong>the</strong> foreground you should have seen a log message similar to <strong>the</strong> following aftersending <strong>the</strong> trap. This is <strong>Zenoss</strong> using <strong>the</strong> MIB to turn <strong>the</strong> trap into an event:Queued event (total of 1) {’sysUpTime.0’: 0L,’firstTime’: 1343072200.36731,’severity’: 3,’eventClassKey’: ’netBotzTempTooHigh’,’oid’: ’1.3.6.1.4.1.5528.100.10.2.1.0.2’,’component’: ’’,’community’: ’public’,’summary’: ’snmp trap netBotzTempTooHigh’,’eventGroup’: ’trap’,’sysUpTime’: 0L,’device’: ’127.0.0.1’,’lastTime’: 1343072200.36731,’monitor’: ’localhost’}You can see how <strong>Zenoss</strong> has maintained <strong>the</strong> numeric OID in <strong>the</strong> event’s oid field. It has also decoded it tonetBotzTempTooHigh using <strong>the</strong> MIB we imported and used that value in <strong>the</strong> eventClassKey and summary fields.3. Find this netBotzTempTooHigh event in web interface’s event console.Send a Full Trap Now that we’ve proved out a simple trap, we should add variable bindings or varbinds to <strong>the</strong> trap.If you look at <strong>the</strong> netBotzTempTooHigh trap in <strong>the</strong> <strong>Zenoss</strong> web interface’s MIB explorer again, you’ll see that <strong>the</strong>re’san extensive list of Objects associated with <strong>the</strong> trap definition. These are variable bindings.A variable binding allows <strong>the</strong> device sending <strong>the</strong> SNMP trap to attach additional information to <strong>the</strong> trap. In thisexample, one of <strong>the</strong> variable bindings for <strong>the</strong> netBotzTempTooHigh trap is netBotzV2TrapSensorID. This will give usa way to know which one of <strong>the</strong> sensors has exceeded it’s high temperature threshold.1. Run <strong>the</strong> following snmptrap command.snmptrap localhost 0 NETBOTZV2-MIB::netBotzTempTooHigh \NETBOTZV2-MIB::netBotzV2TrapSensorID s ’nbHawkEnc_1_TEMP1’As you can see, this zentrap command starts exactly <strong>the</strong> same as in <strong>the</strong> example. We <strong>the</strong>n add <strong>the</strong> followingthree fields.(a) NETBOTZV2-MIB::netBotzV2TrapSensorID (OID)1.1. ZenPack Development Guide 39


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release(b) s (type)(c) ’nbHawkEnc_1_TEMP1’ (value)We can continue to add sets of <strong>the</strong>se three parameters to add as many o<strong>the</strong>r variable bindings to <strong>the</strong> trap as wewant.Assuming you were running zentrap in <strong>the</strong> foreground you should see a log that looks like <strong>the</strong> following:Queued event (total of 1) {’sysUpTime.0’: 0L,’firstTime’: 1343073249.083523,’severity’: 3,’netBotzV2TrapSensorID’: ’nbHawkEnc_1_TEMP1’,’eventClassKey’: ’netBotzTempTooHigh’,’oid’: ’1.3.6.1.4.1.5528.100.10.2.1.0.2’,’component’: ’’,’community’: ’public’,’summary’: ’snmp trap netBotzTempTooHigh’,’eventGroup’: ’trap’,’sysUpTime’: 0L,’device’: ’127.0.0.1’,’lastTime’: 1343073249.083523,’monitor’: ’localhost’}Note that <strong>the</strong> only difference between this event and <strong>the</strong> simple event is <strong>the</strong> addition of <strong>the</strong> net-BotzV2TrapSensorID field. So now you see how <strong>Zenoss</strong> take <strong>the</strong> name/value pairs that are <strong>the</strong> SNMP trap’svariable bindings and turn <strong>the</strong>m into name/value pairs within <strong>the</strong> resulting event.Mapping SNMP Trap EventsNow that we’re able to create SNMP traps anytime we want, it’s time to use <strong>Zenoss</strong>’ event mapping system to make<strong>the</strong>m more useful. The most important field on an incoming event when it comes to mapping is <strong>the</strong> eventClassKeyfield. Fortunately for us, SNMP traps get that great eventClassKey set that gives us a big head start.1. Map <strong>the</strong> event.(a) Navigate to Events in <strong>the</strong> web interface.(b) Select <strong>the</strong> netBotzTempTooHigh event you just created.(c) Click <strong>the</strong> toolbar button that looks like a hierarchy. If you hover over it, <strong>the</strong> tooltip will say Reclassify anevent.(d) Choose <strong>the</strong> /Environ event class <strong>the</strong>n click SUBMITNow <strong>the</strong> next time a netBotzTempTooHigh trap is received it will be put into <strong>the</strong> /Environ event class insteadof /Unknown.2. Enrich <strong>the</strong> event.(a) Click <strong>the</strong> Go to new mapping link to navigate to <strong>the</strong> new mapping.(b) Click Edit in <strong>the</strong> left navigation pane.(c) Set Transform to <strong>the</strong> following:evt.component = getattr(evt, ’netBotzV2TrapSensorID’, ’’)This will use <strong>the</strong> name of <strong>the</strong> sensor as described by <strong>the</strong> netBotzV2TrapSensorID variable binding as <strong>the</strong>event’s component field.1.1. ZenPack Development Guide 40


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseThere are endless possibilities of what you could do within <strong>the</strong> transform for this event and o<strong>the</strong>rs. This is just onepractical example.1.1.6 Monitoring an HTTP APIThe following sections will describe an efficient approach to monitoring data via a HTTP API. We’ll start by usingzenpacklib to more easily extend <strong>the</strong> <strong>Zenoss</strong> object model. Then we’ll use a Python modeler plugin to fill out <strong>the</strong>object model. Then we’ll use PythonCollector to monitor for events, datapoints and even to update <strong>the</strong> model.For purposes of this guide we’ll be building a ZenPack that monitors <strong>the</strong> wea<strong>the</strong>r using The Wea<strong>the</strong>r Channel’s Wea<strong>the</strong>rUnderground API.Note: It is recommended that you have already finished <strong>the</strong> Monitoring an SNMP-Enabled Device section as itprovides much more detail and troubleshooting advice. This exercise builds on that experience.Exercises:Wunderground APIThe Wea<strong>the</strong>r Underground provides an API that can be used to get all sorts of data related to <strong>the</strong> wea<strong>the</strong>r. Before youcan use most endpoints on <strong>the</strong> API you must first create an account. Fortunately you can get a Developer account withall of <strong>the</strong> bells and whistles for free by signing up at http://www.wunderground.com/wea<strong>the</strong>r/api. So go sign up andget your API key. You’ll need it for <strong>the</strong> rest of this exercise.We’ll be using <strong>the</strong> following APIs for this exercise.1. AutoComplete2. Alerts3. ConditionsAutoComplete APIBoth <strong>the</strong> Alerts and Conditions APIs require that you query for a specific location. It can be hard to know what <strong>the</strong>name or code for a location is without doing some manual research. That’s where <strong>the</strong> AutoComplete API comes in.You can provide a reasonable name for a location and it will return a list of possible matches along with a unique linkfor that location.We’ll use <strong>the</strong> AutoComplete API during modeling so that <strong>the</strong> <strong>Zenoss</strong> user can enter nearly any city or county name<strong>the</strong>n let <strong>Zenoss</strong> do <strong>the</strong> work of converting that into <strong>the</strong> link that we’ll subsequently use to query for wea<strong>the</strong>r alerts andconditions.Here’s an example query for Austin, TX:http://autocomplete.wunderground.com/aq?query=Austin%2C%20TXNote: “Austin%2C%20TX” is <strong>the</strong> URL encoded version of “Austin, TX”. We won’t have to worry about that whenwe work with it because our HTTP library automatically encodes URLs.Here’s <strong>the</strong> response to that example query for Austin, TX:{"RESULTS": [{"c": "US",1.1. ZenPack Development Guide 41


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release}]}"l": "/q/zmw:78701.1.99999","lat": "30.271158","ll": "30.271158 -97.741699","lon": "-97.741699","name": "Austin, Texas","type": "city","tz": "America/Chicago","tzs": "CDT","zmw": "78701.1.99999"There are a few things to note about this request and response. The first is that we didn’t need to use our API key.This is because <strong>the</strong> AutoComplete API doesn’t require an API key. The second is that <strong>the</strong>re’s only a single result forAustin, TX. The third is <strong>the</strong> l value which is <strong>the</strong> unique link to Austin, TX that we can use when accessing <strong>the</strong> o<strong>the</strong>rAPI endpoints such as Alerts and Conditions.Alerts APIThe Alerts API provides information about severe wea<strong>the</strong>r alerts such as tornado warnings, flood warnings and o<strong>the</strong>rspecial wea<strong>the</strong>r statements. We’ll be collecting <strong>the</strong>se alerts to create corresponding <strong>Zenoss</strong> events. This way operatorscan know when severe wea<strong>the</strong>r may be impacting areas of concern.Here’s an example query for alerts in Austin, TX:http://api.wunderground.com/api//alerts/q/zmw:78701.1.99999.jsonNote: Note how <strong>the</strong> URL ends with /alerts/.json using <strong>the</strong> l link value from <strong>the</strong> AutoComplete query for Austin,TX above.Here’s <strong>the</strong> relevant portion of <strong>the</strong> response to an alerts query. Of course Austin doesn’t have severe wea<strong>the</strong>r so we’llbe looking at Des Moines alerts instead:{}"alerts": [{"date": "1:07 PM CDT on June 16, 2014","date_epoch": "1402942020","description": "Severe Thunderstorm Warning","expires": "2:15 PM CDT on June 16, 2014","expires_epoch": "1402946100","message": "\nThe National Wea<strong>the</strong>r Service in Des Moines has issued a\n\n* Severe Thunder"phenomena": "SV","significance": "W","type": "WRN","tz_long": "America/Chicago","tz_short": "CDT"}]It’s easy to imagine turning this alert into a <strong>Zenoss</strong> event. We’ll see how to do this a bit later. The Alerts APIdocumentation has a link to a document that describes what <strong>the</strong> phenomena, significance, and type values represent.1.1. ZenPack Development Guide 42


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseConditions APIThe Conditions API provides information about current wea<strong>the</strong>r conditions for a given location. The Conditions APIis used in exactly <strong>the</strong> same way as <strong>the</strong> Alerts API, and accepts <strong>the</strong> same link to specify <strong>the</strong> location. There’s a lot ofnumeric data that would be useful to graph and threshold as <strong>Zenoss</strong> datapoints.Here’s an example query for conditions in Austin, TX:http://api.wunderground.com/api//conditions/q/zmw:78701.1.99999.jsonHere’s <strong>the</strong> relevant portion of <strong>the</strong> response to a conditions query:{"current_observation": {"UV": "1","dewpoint_c": 11,"dewpoint_f": 51,"dewpoint_string": "51 F (11 C)","display_location": {"city": "San Francisco","country": "US","country_iso3166": "US","elevation": "47.00000000","full": "San Francisco, CA","latitude": "37.77500916","longitude": "-122.41825867","magic": "1","state": "CA","state_name": "California","wmo": "99999","zip": "94101"},"estimated": {},"feelslike_c": "13.9","feelslike_f": "57.0","feelslike_string": "57.0 F (13.9 C)","forecast_url": "http://www.wunderground.com/US/CA/San_Francisco.html","heat_index_c": "NA","heat_index_f": "NA","heat_index_string": "NA","history_url": "http://www.wunderground.com/wea<strong>the</strong>rstation/WXDailyHistory.asp?ID=KCASANFR58","icon": "partlycloudy","icon_url": "http://icons.wxug.com/i/c/k/partlycloudy.gif","image": {"link": "http://www.wunderground.com","title": "Wea<strong>the</strong>r Underground","url": "http://icons.wxug.com/graphics/wu2/logo_130x80.png"},"local_epoch": "1402931138","local_time_rfc822": "Mon, 16 Jun 2014 08:05:38 -0700","local_tz_long": "America/Los_Angeles","local_tz_offset": "-0700","local_tz_short": "PDT","nowcast": "","ob_url": "http://www.wunderground.com/cgi-bin/findwea<strong>the</strong>r/getForecast?query=37.773285,-122.4"observation_epoch": "1402931132","observation_location": {"city": "SOMA - Near Van Ness, San Francisco","country": "US",1.1. ZenPack Development Guide 43


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release}}"country_iso3166": "US","elevation": "49 ft","full": "SOMA - Near Van Ness, San Francisco, California","latitude": "37.773285","longitude": "-122.417725","state": "California"},"observation_time": "Last Updated on June 16, 8:05 AM PDT","observation_time_rfc822": "Mon, 16 Jun 2014 08:05:32 -0700","precip_1hr_in": "0.00","precip_1hr_metric": " 0","precip_1hr_string": "0.00 in ( 0 mm)","precip_today_in": "0.00","precip_today_metric": "0","precip_today_string": "0.00 in (0 mm)","pressure_in": "29.89","pressure_mb": "1012","pressure_trend": "+","relative_humidity": "81%","solarradiation": "--","station_id": "KCASANFR58","temp_c": 13.9,"temp_f": 57.0,"temperature_string": "57.0 F (13.9 C)","visibility_km": "16.1","visibility_mi": "10.0","wea<strong>the</strong>r": "Scattered Clouds","wind_degrees": 238,"wind_dir": "WSW","wind_gust_kph": 0,"wind_gust_mph": 0,"wind_kph": 4.8,"wind_mph": 3.0,"wind_string": "From <strong>the</strong> WSW at 3.0 MPH","windchill_c": "NA","windchill_f": "NA","windchill_string": "NA"Using ZenPackLib (zenpacklib)In <strong>the</strong> Device Modeling and Component Modeling sections of <strong>the</strong> Monitoring an SNMP-Enabled Device documentationwe covered what could be called <strong>the</strong> “long way” to extend <strong>the</strong> <strong>Zenoss</strong> object model to add <strong>the</strong> new NetBotzDevicedevice type, it’s TemperatureSensor component type and <strong>the</strong> relationship between <strong>the</strong>m. A new library called zenpacklibis now available that we use to make this process easier.In this case we want to add a new WundergroundDevice device type, a WundergroundLocation component type anda relationship between <strong>the</strong>m so we can add a Wea<strong>the</strong>r Underground device such as wunderground.com to <strong>Zenoss</strong> andadd one or more locations where we want to monitor wea<strong>the</strong>r conditions. This section will describe how we do thatusing zenpacklib.Create <strong>the</strong> Wea<strong>the</strong>r Underground ZenPackSince we don’t yet have a Wea<strong>the</strong>r Underground ZenPack to work with, let’s create that first.1.1. ZenPack Development Guide 44


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release1. Go to <strong>the</strong> Advanced -> Settings -> ZenPacks in <strong>the</strong> <strong>Zenoss</strong> web interface.2. Choose Create a ZenPack... from <strong>the</strong> gear menu above <strong>the</strong> list of ZenPacks.3. Name <strong>the</strong> ZenPack ZenPacks.training.Wea<strong>the</strong>rUnderground <strong>the</strong>n click OK.Download zenpacklibCreating <strong>the</strong> ZenPack will create <strong>the</strong> ZenPack’s source directory under $ZEN-HOME/ZenPacks/ZenPacks.training.Wea<strong>the</strong>rUnderground. Run <strong>the</strong> following commands as <strong>the</strong> zenoss user tomake this directory, and it’s important subdirectory more easily accessible:export ZP_TOP_DIR=$ZENHOME/ZenPacks/ZenPacks.training.Wea<strong>the</strong>rUndergroundexport ZP_DIR=$ZP_TOP_DIR/ZenPacks/training/Wea<strong>the</strong>rUndergroundThen download <strong>the</strong> latest zenpacklib from GitHub into <strong>the</strong> ZenPack’s main code directory with <strong>the</strong> following commands.cd $ZP_DIRwget https://raw.githubusercontent.com/zenoss/zenpacklib/master/zenpacklib.pyCreate <strong>the</strong> ZenPackSpecOnce zenpacklib.py is in $ZP_DIR you can create what is called a ZenPackSpec in <strong>the</strong> ZenPack’s __init__.py. ThisZenPackSpec will define <strong>the</strong> specification for <strong>the</strong> ZenPack. Specifically its name, zProperties, classes and class relationships.Replace <strong>the</strong> contents of $ZP_DIR/__init__.py with <strong>the</strong> following:# Import zenpacklib from <strong>the</strong> current directory (zenpacklib.py).from . import zenpacklib# Create a ZenPackSpec and name it CFG.CFG = zenpacklib.ZenPackSpec(name=__name__,zProperties={’DEFAULTS’: {’category’: ’Wea<strong>the</strong>r Underground’},’zWundergroundAPIKey’: {},},’zWundergroundLocations’: {’type’: ’lines’,’default’: [’Austin, TX’, ’San Jose, CA’, ’Annapolis, MD’],},classes={’WundergroundDevice’: {’base’: zenpacklib.Device,’label’: ’Wea<strong>the</strong>r Underground API’,},’WundergroundLocation’: {’base’: zenpacklib.Component,’label’: ’Location’,1.1. ZenPack Development Guide 45


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release’properties’: {’country_code’: {’label’: ’Country Code’,’order’: 4.0,},’timezone’: {’label’: ’Time Zone’,’order’: 4.1,},},},}’api_link’: {’label’: ’API Link’,’order’: 4.9,’grid_display’: False,},)class_relationships=zenpacklib.relationships_from_yuml("""[WundergroundDevice]++-[WundergroundLocation]""")# Create <strong>the</strong> specification.CFG.create()You can see this ZenPackSpec defines <strong>the</strong> following important aspects of our ZenPack.1. The name is set to __name__ which will evaluate to ZenPacks.training.Wea<strong>the</strong>rUnderground. This shouldalways be set in this way as it will helpfully figure out <strong>the</strong> name for you.2. The zProperties contains configuration properties we want <strong>the</strong> ZenPack to add to <strong>the</strong> <strong>Zenoss</strong> system when it isinstalled.Note that DEFAULTS is not added as configuration property. It is a special value that will cause it’s properties tobe added as <strong>the</strong> default for all of <strong>the</strong> o<strong>the</strong>r listed zProperties. Specifically in this case it will cause <strong>the</strong> categoryof zWundergroundAPIKey and zWundergroundLocations to be set to Wea<strong>the</strong>r Underground. This is aconvenience to avoid having to repeatedly type <strong>the</strong> category for each added property.The zWundergroundAPIKey zProperty has an empty dictionary ({}). This is because we want it to be a stringtype with an empty default value. These happen to be <strong>the</strong> defaults so <strong>the</strong>y don’t need to be specified.The zWundergroundLocations property uses <strong>the</strong> lines type which allows <strong>the</strong> user to specify multiple lines of text.Each line will be turned into an element in a list which you can see is also how <strong>the</strong> default value is specified. Theidea here is that unless <strong>the</strong> user configures o<strong>the</strong>rwise, we will default to monitoring wea<strong>the</strong>r alerts and conditionsfor Austin, TX, San Jose, CA, and Annapolis, MD.3. The classes contains each of <strong>the</strong> object classes we want <strong>the</strong> ZenPack to add.In this case we’re adding WundergroundDevice which because base is set to zenpacklib.Device will be a subclassor specialization of <strong>the</strong> standard <strong>Zenoss</strong> device type. We’re also adding WundergroundLocation which becausebase is set to zenpacklib.Component will be a subclass of <strong>the</strong> standard component type.The label for each is simply <strong>the</strong> human-friendly name that will be used to refer to <strong>the</strong> resulting objects when<strong>the</strong>y’re seen in <strong>the</strong> <strong>Zenoss</strong> web interface.The properties for WundergroundLocation are extra bits of data we want to model from <strong>the</strong> API and show to <strong>the</strong>user in <strong>the</strong> web interface. order will be used to show <strong>the</strong> properties in <strong>the</strong> defined order, and setting grid_display1.1. ZenPack Development Guide 46


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Releaseto false for api_link will allow it be shown in <strong>the</strong> details panel of <strong>the</strong> component, but not in <strong>the</strong> component grid.4. class_relationships uses <strong>the</strong> relationships_from_yuml helper to succinctly define a relationship stating WundergroundDevicecan contain many WundergroundLocation objects.5. Finally CFG.create() create what has been defined in <strong>the</strong> ZenPackSpec. Without this call, nothing wouldhappen.Reinstall <strong>the</strong> ZenPackBecause you added new zProperties you must now reinstall <strong>the</strong> ZenPack. This is required because new zPropertiesonly get installed into <strong>Zenoss</strong> when <strong>the</strong> ZenPack is installed. Everything else in <strong>the</strong> ZenPackSpec is created each time<strong>Zenoss</strong> starts.Run <strong>the</strong> following command to reinstall <strong>the</strong> ZenPack and keep it in development mode.zenpack --link --install $ZP_TOP_DIRPython Modeler PluginNow that we’ve created a WundergroundLocation component type, we need to create a modeler plugin to createlocations in <strong>the</strong> database. We’re dealing with a custom HTTP API, so we’ll want to base our modeler plugin on <strong>the</strong>PythonPlugin class. This gives us full control of both <strong>the</strong> collection and processing of <strong>the</strong> modeling data.The modeler plugin will pass each location <strong>the</strong> user has specified in <strong>the</strong> zWundergroundLocations property to Wea<strong>the</strong>rUnderground’s AutoComplete API to retrieve some basic information about <strong>the</strong> location, and very importantly <strong>the</strong> l(link) that uniquely identifies <strong>the</strong> location. The link will later be used to monitor <strong>the</strong> alerts and conditions for <strong>the</strong>location.Create Wea<strong>the</strong>rUnderground.Locations Modeler PluginUse <strong>the</strong> following steps to create our modeler plugin.1. Make <strong>the</strong> directory that will contain our modeler plugin.mkdir -p $ZP_DIR/modeler/plugins/Wea<strong>the</strong>rUnderground2. Create __init__.py or dunder-init files.touch $ZP_DIR/modeler/__init__.pytouch $ZP_DIR/modeler/plugins/__init__.pytouch $ZP_DIR/modeler/plugins/Wea<strong>the</strong>rUnderground/__init__.pyThese empty __init__.py files are mandatory if we ever expect Python to import modules from <strong>the</strong>se directories.3. Create $ZP_DIR/modeler/plugins/Wea<strong>the</strong>rUnderground/Locations.py with <strong>the</strong> followingcontents."""Models locations using <strong>the</strong> Wea<strong>the</strong>r Underground API."""# stdlib Importsimport json# Twisted Importsfrom twisted.internet.defer import inlineCallbacks, returnValuefrom twisted.web.client import getPage1.1. ZenPack Development Guide 47


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release# <strong>Zenoss</strong> Importsfrom Products.DataCollector.plugins.CollectorPlugin import PythonPluginclass Locations(PythonPlugin):"""Wea<strong>the</strong>r Underground locations modeler plugin."""relname = ’wundergroundLocations’modname = ’ZenPacks.training.Wea<strong>the</strong>rUnderground.WundergroundLocation’requiredProperties = (’zWundergroundAPIKey’,’zWundergroundLocations’,)deviceProperties = PythonPlugin.deviceProperties + requiredProperties@inlineCallbacksdef collect(self, device, log):"""Asynchronously collect data from device. Return a deferred."""log.info("%s: collecting data", device.id)apikey = getattr(device, ’zWundergroundAPIKey’, None)if not apikey:log.error("%s: %s not set. Get one from http://www.wunderground.com/wea<strong>the</strong>r/api",device.id,’zWundergroundAPIKey’)returnValue(None)locations = getattr(device, ’zWundergroundLocations’, None)if not locations:log.error("%s: %s not set.",device.id,’zWundergroundLocations’)returnValue(None)rm = self.relMap()for location in locations:try:response = yield getPage(’http://autocomplete.wunderground.com/aq?query={query}’.format(query=location))response = json.loads(response)except Exception, e:log.error("%s: %s", device.id, e)returnValue(None)for result in response[’RESULTS’]:rm.append(self.objectMap({1.1. ZenPack Development Guide 48


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleasereturnValue(rm)’id’: self.prepId(result[’zmw’]),’title’: result[’name’],’api_link’: result[’l’],’country_code’: result[’c’],’timezone’: result[’tzs’],}))def process(self, device, results, log):"""Process results. Return iterable of datamaps or None."""return resultsWhile it looks like <strong>the</strong>re’s quite a bit of code in this modeler plugin, a lot of that is <strong>the</strong> kind of error handlingyou’d want to do in a real modeler plugin. Let’s walk through some of <strong>the</strong> highlights.(a) ImportsWe import <strong>the</strong> standard json module because <strong>the</strong> Wea<strong>the</strong>r Underground API returns json-encoded responses.We import inlineCallBacks and returnValue because <strong>the</strong> PythonPlugin.collect method should return a Deferredso that it can be executed asynchronously by zenmodeler. You don’t need to use inlineCallbacks, butI find it to be a nice way to make Twisted’s asynchronous callback-based code look more procedural andbe easier to understand. I recommend Dave Peticolas’ excellent Twisted Introduction for learning moreabout Twisted. inlineCallback is covered in part 17.We also import Twisted’s getPage function. This is an extremely easy to use function for asynchronouslyfetching a URL.We import PythonPlugin because it will be <strong>the</strong> base class for our modeler plugin class. It’s <strong>the</strong> best choicefor modeling data from HTTP APIs.(b) Locations ClassRemember that your modeler plugin’s class name must match <strong>the</strong> filename or <strong>Zenoss</strong> won’t be able to loadit. So because we named <strong>the</strong> file Locations.py we must name <strong>the</strong> class Locations.(c) relname and modname PropertiesThese should be defined in this way for modeler plugins that fill a single relationship like we’re doing inthis case. It states that this modeler plugin creates objects in <strong>the</strong> device’s wundergroundLocations relationship,and that it creates objects of <strong>the</strong> ZenPacks.training.Wea<strong>the</strong>rUnderground.WundergroundLocationtype within this relationship.Where does relname come from? It comes from <strong>the</strong> [WundergroundDevice]++-[WundergroundLocation]relationship we defined in __init__.py. Because it’s a to-many relationship to <strong>the</strong> WundergroundLocationtype, zenpacklib will name <strong>the</strong> relationship by lowercasing <strong>the</strong> first letter and adding an “s” to <strong>the</strong>end to make it plural.Where does modname come from? It will be ..So because we defined <strong>the</strong> WundergroundLocation class in __init__.py, and <strong>the</strong>ZenPack’s name is ZenPacks.training.Wea<strong>the</strong>rUnderground, <strong>the</strong> modname will be Zen-Packs.training.Wea<strong>the</strong>rUnderground.WundergroundLocation.(d) deviceProperties PropertiesThe class’ deviceProperties property provides a way to get additional device properties available to yourmodeler plugin’s collect and process methods. The default properties that will be available for a Python-Plugin are: id, manageIp, _snmpLastCollection, _snmpStatus, and zCollectorClientTimeout. Our modeler1.1. ZenPack Development Guide 49


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Releaseplugin will also need to know what values <strong>the</strong> user has set for zWundergroundAPIKey and zWunderground-Locations. So we add those to <strong>the</strong> defaults.(e) collect MethodThe collect method is something PythonPlugin has, but o<strong>the</strong>r base modeler plugin types like SnmpPlugindon’t. This is because you must write <strong>the</strong> code to collect <strong>the</strong> data to be processed, and that’s exactly whatyou should do in <strong>the</strong> collect method.While <strong>the</strong> collect method can return ei<strong>the</strong>r normal results or a Deferred, it is highly recommend to returna Deferred to keep zenmodeler from blocking while your collect method executes. In this examplewe’ve decorated <strong>the</strong> method with @inlineCallbacks and have returned out data at <strong>the</strong> endwith returnValue(rm). This causes it to return a Deferred. By decorating <strong>the</strong> method with@inlineCallbacks we’re able to make an asynchronous request to <strong>the</strong> Wea<strong>the</strong>r Underground APIwith response = yield getPage(...).The first thing we do in <strong>the</strong> collect method is log an informational message to let <strong>the</strong> user know whatwe’re doing. This log will appear in zenmodeler.log, or on <strong>the</strong> console if we run zenmodeler in <strong>the</strong>foreground, or in <strong>the</strong> web interface when <strong>the</strong> user manually remodels <strong>the</strong> device.Next we make sure that <strong>the</strong> user has configured a value for zWundergroundAPIKey. This isn’t strictlynecessary here because <strong>the</strong> modeler plugin only uses Wea<strong>the</strong>r Underground’s AutoComplete API whichdoesn’t require an API key. I put this check here because I didn’t want to get into a situation where <strong>the</strong>locations modeled successfully, but <strong>the</strong>n failed to collect because an API key wasn’t set.Next we make suer that <strong>the</strong> user as configured at least one location in zWundergroundLocations. This ismandatory because this controls what locations will be modeled.Next we create rm which is a common convention we use in modeler plugins and stands for RelationshipMap.Because we set <strong>the</strong> relname and modname class properties this will create a RelationshipMapwith it’s relname and modname set to <strong>the</strong> same.Now we iterate through each location making a call to <strong>the</strong> AutoComplete API for each. For each matchinglocation in <strong>the</strong> response we will append an ObjectMap to rm with some key properties set.• id is mandatory and should be set to a value unique to all components on <strong>the</strong> device. If you look back<strong>the</strong> example AutoComplete response you’ll see that <strong>the</strong> zmw property is useful for this purpose. Notethat prepId should always be used for id. It will make any string safe to use as a <strong>Zenoss</strong> id.• title will default to <strong>the</strong> value of id if it isn’t set. It’s usually a good idea to explicitly set it as we’redoing here. It should be a human-friendly label for <strong>the</strong> component. The location’s name is a goodcandidate for this. It will look something like “Austin, Texas”.• api_link is a property we defined for <strong>the</strong> WundergroundLocation class in __init__.py. This iswhere we’ll store <strong>the</strong> returned link or l property. This will be important for monitoring <strong>the</strong> alerts andconditions of <strong>the</strong> location later on.• country_code is ano<strong>the</strong>r property we defined. It’s purely informational and will simply be shown to<strong>the</strong> user when <strong>the</strong>y’re viewing <strong>the</strong> location in <strong>the</strong> web interface.• timezeone is ano<strong>the</strong>r property we defined just for informational purposes.(f) process MethodThe process method is usually where you take <strong>the</strong> data in <strong>the</strong> results argument and process it into DataMapsto return. However, in <strong>the</strong> case of PythonPlugin modeler plugins, <strong>the</strong> data returned from <strong>the</strong> collect methodwill be passed into process as <strong>the</strong> results argument. In this case that is already complete processed data.So we just return it.4. Restart <strong>Zenoss</strong>.1.1. ZenPack Development Guide 50


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseAfter adding a new modeler plugin you must restart <strong>Zenoss</strong>. If you’re following <strong>the</strong> Running a Minimal <strong>Zenoss</strong>instructions you really only need to restart zopectl and zenhub.That’s it. The modeler plugin has been created. Now we just need to do some <strong>Zenoss</strong> configuration to allow us to useit.Add Wea<strong>the</strong>rUnderground Device ClassTo support adding our special WundergroundDevice devices that we defined in __init__.py to <strong>Zenoss</strong> we mustcreate a new device class. This will give us control of <strong>the</strong> zPythonClass configuration property that defines what typeof devices will be created. It will also allow us to control what modeler plugins and monitoring templates will be used.Use <strong>the</strong> following steps to add <strong>the</strong> device class.1. Navigate to <strong>the</strong> Infrastructure view.2. Select <strong>the</strong> root of <strong>the</strong> DEVICES tree.3. Click <strong>the</strong> + button at <strong>the</strong> bottom of <strong>the</strong> list to add a new organizer.4. Set <strong>the</strong> Name to Wea<strong>the</strong>rUnderground <strong>the</strong>n click SUBMIT.The new Wea<strong>the</strong>rUnderground device will now be selected. We’ll want to check on some important configurationproperties using <strong>the</strong> following steps.Set Device Class Properties1. Click <strong>the</strong> DETAILS button at <strong>the</strong> top of <strong>the</strong> list.2. Select Configuration Properties. Set <strong>the</strong> following properties.• zPythonClass: ZenPacks.training.Wea<strong>the</strong>rUnderground.WundergroundDevice• zPingMonitorIgnore: true• zSnmpMonitorIgnore: true3. Select Modeler Plugins from <strong>the</strong> left navigation pane.You’ll likely find a list of selected modeler plugins that looks something like <strong>the</strong> following.• zenoss.snmp.NewDeviceMap• zenoss.snmp.DeviceMap• zenoss.snmp.InterfaceMap• zenoss.snmp.RouteMap4. Remove all of <strong>the</strong> modeler plugins from <strong>the</strong> Selected list.5. Move Wea<strong>the</strong>rUnderground.Locations from <strong>the</strong> Available to <strong>the</strong> Selected list.6. Click Save.Add <strong>the</strong> Wea<strong>the</strong>rUnderground Device Class to <strong>the</strong> ZenPack Now that we’ve setup <strong>the</strong> Wea<strong>the</strong>rUnderground deviceclass, it’s time to add it to our ZenPack using <strong>the</strong> following steps. Adding a device class to your ZenPack causesall settings in that device class to be added to <strong>the</strong> ZenPack. This includes modeler plugin configuration, configurationproperty values and monitoring templates.1. Make sure that you have <strong>the</strong> Wea<strong>the</strong>rUnderground device class selected in <strong>the</strong> Infrastructure view.2. Choose Add to ZenPack from <strong>the</strong> gear menu in <strong>the</strong> bottom-left.1.1. ZenPack Development Guide 51


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release3. Select ZenPacks.training.Wea<strong>the</strong>rUnderground <strong>the</strong>n click SUBMIT.4. Export <strong>the</strong> ZenPack. (Exporting a ZenPack)Add <strong>the</strong> wunderground.com Device This would be a good time to add a device to <strong>the</strong> new device class. There aremany ways to add devices to <strong>Zenoss</strong>, but if you’re Running a Minimal <strong>Zenoss</strong> you may not be running zenjobs andsome of <strong>the</strong>m won’t work. During ZenPack development it’s often easiest to use zendisc to add devices.Run <strong>the</strong> following command to add a wunderground.com device.zendisc run --deviceclass=/Wea<strong>the</strong>rUnderground --device=wunderground.comYou should see output similar to <strong>the</strong> following:INFO zen.ZenModeler: Collecting for device wunderground.comINFO zen.ZenModeler: No WMI plugins found for wunderground.comINFO zen.ZenModeler: Python collection device wunderground.comINFO zen.ZenModeler: plugins: Wea<strong>the</strong>rUnderground.LocationsINFO zen.PythonClient: wunderground.com: collecting dataERROR zen.PythonClient: wunderground.com: zWundergroundAPIKey not set. Get one from http://www.wunderINFO zen.PythonClient: Python client finished collection for wunderground.comWARNING zen.ZenModeler: The plugin Wea<strong>the</strong>rUnderground.Locations returned no results.INFO zen.ZenModeler: No change in configuration detectedINFO zen.ZenModeler: No command plugins found for wunderground.comINFO zen.ZenModeler: SNMP monitoring off for wunderground.comINFO zen.ZenModeler: No portscan plugins found for wunderground.comINFO zen.ZenModeler: Scan time: 0.02 secondsINFO zen.ZenModeler: Daemon ZenModeler shutting downNote: The error about zWundergroundAPIKey not being set is expected because we haven’t set it. The solutionis to go to <strong>the</strong> wunderground.com device in <strong>the</strong> web interface and add your API key to <strong>the</strong> zWundergroundAPIKeyconfiguration property. After adding <strong>the</strong> API key you should remodel <strong>the</strong> device.Ano<strong>the</strong>r good way to add device to <strong>Zenoss</strong> is with zenbatchload. Using zenbatchload also allows us to set configurationproperties such as zWundergroundAPIKey as <strong>the</strong> device is added.Create a wunderground.zenbatchload file with <strong>the</strong> following contents:/Devices/Wea<strong>the</strong>rUndergroundwunderground.com zWundergroundAPIKey=’’, zWundergroundLocations=[’Austin, TX’, ’Des MoiNow run <strong>the</strong> following command to load from that file:zenbatchload wunderground.zenbatchloadYou should now be able to see a list of locations on <strong>the</strong> wunderground.com device!Using PythonCollectorThe PythonCollector ZenPack adds <strong>the</strong> capability to write high performance datasources in Python. They will becollected by <strong>the</strong> zenpython daemon that comes with <strong>the</strong> PythonCollector ZenPack.I’d recommend reading <strong>the</strong> PythonCollector <strong>Documentation</strong> for more information.1.1. ZenPack Development Guide 52


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseInstalling PythonCollectorThe first thing we’ll need to do is to make sure <strong>the</strong> PythonCollector ZenPack is installed on our system. If it isn’t,follow <strong>the</strong>se instructions to install it.1. Download <strong>the</strong> latest release from <strong>the</strong> PythonCollector page.2. Run <strong>the</strong> following command to install <strong>the</strong> ZenPack:zenpack --install ZenPacks.zenoss.PythonCollector-.egg3. Restart <strong>Zenoss</strong>.Add PythonCollector Dependency Since we’re going to be using PythonCollector capabilities in our ZenPack wemust now update our ZenPack to define <strong>the</strong> dependency.Follow <strong>the</strong>se instructions to define <strong>the</strong> dependency.1. Navigate to Advanced -> Settings -> ZenPacks.2. Click into <strong>the</strong> ZenPacks.training.Wea<strong>the</strong>rUnderground ZenPack.3. Check ZenPacks.zenoss.PythonCollector in <strong>the</strong> list of dependencies.4. Click Save.5. Export <strong>the</strong> ZenPack. (Exporting a ZenPack)Python Collector Plugin (Events)Now that we have one or more locations modeled on our wunderground.com device, we’ll want to start monitoringeach location. Using PythonCollector we have <strong>the</strong> ability to create events, record datapoints and even update <strong>the</strong>model. We’ll start with an example that creates events from wea<strong>the</strong>r alert data.The idea will be that we’ll create events for locations that have outstanding wea<strong>the</strong>r alerts such as tornado warnings.We’ll try to capture severity data so tornado warnings are higher severity events than something like a frost advisory.Create Alerts Data Source PluginTo make <strong>Zenoss</strong> able to run <strong>the</strong> data source plugin we’re going to create, we must first make sure <strong>the</strong> PythonCollectorZenPack is installed, and that our ZenPack depends on it. See <strong>the</strong> Using PythonCollector section if you haven’t alreadydone this.Follow <strong>the</strong>se steps to create <strong>the</strong> Alerts data source plugin:1. Create $ZP_DIR/dsplugins.py with <strong>the</strong> following contents."""Monitors current conditions using <strong>the</strong> Wea<strong>the</strong>r Underground API."""# Loggingimport loggingLOG = logging.getLogger(’zen.Wea<strong>the</strong>rUnderground’)# stdlib Importsimport jsonimport time# Twisted Importsfrom twisted.internet.defer import inlineCallbacks, returnValue1.1. ZenPack Development Guide 53


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Releasefrom twisted.web.client import getPage# PythonCollector Importsfrom ZenPacks.zenoss.PythonCollector.datasources.PythonDataSource import (PythonDataSourcePlugin,)class Alerts(PythonDataSourcePlugin):"""Wea<strong>the</strong>r Underground alerts data source plugin."""@classmethoddef config_key(cls, datasource, context):return (context.device().id,datasource.getCycleTime(context),context.id,’wunderground-alerts’,)@classmethoddef params(cls, datasource, context):return {’api_key’: context.zWundergroundAPIKey,’api_link’: context.api_link,’location_name’: context.title,}@inlineCallbacksdef collect(self, config):data = self.new_data()for datasource in config.datasources:try:response = yield getPage(’http://api.wunderground.com/api/{api_key}/alerts{api_link}.json’.format(api_key=datasource.params[’api_key’],api_link=datasource.params[’api_link’]))response = json.loads(response)except Exception:LOG.exception("%s: failed to get alerts data for %s",config.id,datasource.location_name)continuefor alert in response[’alerts’]:severity = Noneif int(alert[’expires_epoch’])


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Releaseseverity = 2data[’events’].append({’device’: config.id,’component’: datasource.component,’severity’: severity,’eventKey’: ’wu-alert-{}’.format(alert[’type’]),’eventClassKey’: ’wu-alert’,returnValue(data)’summary’: alert[’description’],’message’: alert[’message’],’wu-description’: alert[’description’],’wu-date’: alert[’date’],’wu-expires’: alert[’expires’],’wu-phenomena’: alert[’phenomena’],’wu-significance’: alert[’significance’],’wu-type’: alert[’type’],})Let’s walk through this code to explain what is being done.(a) LoggingThe first thing we do is import logging and create LOG as our logger. It’s important that <strong>the</strong> name of <strong>the</strong>logger in <strong>the</strong> logging.getLogger() begins with zen.. You will not see your logs o<strong>the</strong>rwise.The stdlib and Twisted imports are almost identical to what we used in <strong>the</strong> modeler plugin, and <strong>the</strong>y’reused for <strong>the</strong> same purposes.Finally we import PythonDataSourcePlugin from <strong>the</strong> PythonCollector ZenPack. This is <strong>the</strong> class our datasource plugin will extend, and basically allows us to write code that will be executed by <strong>the</strong> zenpythoncollector daemon.(b) Alerts ClassUnlike our modeler plugin, <strong>the</strong>re’s no need to make <strong>the</strong> plugin class’ name <strong>the</strong> same as <strong>the</strong> filename. Aswe’ll see later when we’re setting up <strong>the</strong> monitoring template that will use this plugin, <strong>the</strong>re’s no specificname for <strong>the</strong> file or <strong>the</strong> class required because we configure where to find <strong>the</strong> plugin in <strong>the</strong> datasourceconfiguration within <strong>the</strong> monitoring template.(c) config_key Class MethodThe config_key method must have <strong>the</strong> @classmethod decorator. It is passed datasource, and context.The datasource argument will be <strong>the</strong> actual datasource that <strong>the</strong> user configures in <strong>the</strong> monitoring templatessection of <strong>the</strong> web interface. It has properties such as eventClass, severity, and as you can see a getCycleTime()method that returns <strong>the</strong> interval at which it should be polled. The context argument will be <strong>the</strong>object to which <strong>the</strong> monitoring template and datasource is bound. In our case this will be a location objectsuch as Austin, TX.The purpose of <strong>the</strong> config_key method is to split monitoring configuration into tasks that will be executedby <strong>the</strong> zenpython daemon. The zenpython daemon will create one task for each unique value returned fromconfig_key. It should be used to optimize <strong>the</strong> way data is collected. In some cases it is possible to make asingle query to an API to get back data for many components. In <strong>the</strong>se cases it would be wise to removecontext.id from <strong>the</strong> config_key so we get one task for all components.In our case, <strong>the</strong> Wea<strong>the</strong>r Underground API must be queried once per location so it makes more sense toput context.id in <strong>the</strong> config_key so we get one task per location.1.1. ZenPack Development Guide 55


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseThe value returned by config_key will be used when zenpython logs. So adding something likewunderground-alerts to <strong>the</strong> end makes it easy to see logs related to collecting alerts in <strong>the</strong> log file.The config_key method will only be executed by zenhub. So you must restart zenhub if you make changesto <strong>the</strong> config_key method. This also means that if <strong>the</strong>re’s an exception in <strong>the</strong> config_key method it willappear in <strong>the</strong> zenhub log, not zenpython.(d) params Class MethodThe params method must have <strong>the</strong> @classmethod decorator. It is passed <strong>the</strong> same datasource andcontext arguments as config_key.The purpose of <strong>the</strong> params method is to copy information from <strong>the</strong> <strong>Zenoss</strong> database into <strong>the</strong> config.datasources[*]that will be passed as an argument to <strong>the</strong> collect method. Since <strong>the</strong> collect methodis run by zenpython it won’t have direct access to <strong>the</strong> database, so it relies on <strong>the</strong> params method toprovide it with any information it will need to collect.In our case you can see that we’re copying <strong>the</strong> context’s zWundergroundAPIKey, api_link and title properties.All of <strong>the</strong>se will be used in <strong>the</strong> collect method.Just like <strong>the</strong> config_key method, params will only be executed by zenhub. So be sure to restart zenhub ifyou make changes, and look in <strong>the</strong> zenhub log for errors.(e) collect MethodThe collect method does all of <strong>the</strong> real work. It will be called once per cycletime. It gets passed a configargument which for <strong>the</strong> most part has two useful properties: config.id and config.datasources. config.idwill be <strong>the</strong> device’s id, and config.datasources is a list of <strong>the</strong> datasources that need to be collected.You’ll see in <strong>the</strong> collect method that each datasource in config.datasources has some useful properties.datasource.component will be <strong>the</strong> id of <strong>the</strong> component against which <strong>the</strong> datasource is run, or blank in<strong>the</strong> case of a device-level monitoring template. datasource.params contains whatever <strong>the</strong> params methodreturned.Within <strong>the</strong> body of <strong>the</strong> collect method we see that we create a new data variable using data =self.new_data(). data is a place where we stick all of <strong>the</strong> collected events, values and maps. datalooks like <strong>the</strong> following:data = {’events’: [],’values’: defaultdict(, {}),’maps’: [],}Next we iterate over every configured datasource. For each one we make a call to Wea<strong>the</strong>r Underground’sAlerts API, <strong>the</strong>n iterate over each alert in <strong>the</strong> response creating an event for each.The following standard fields are being set for every event. You should read <strong>Zenoss</strong>’ event managementdocumentation if <strong>the</strong> purpose of any of <strong>the</strong>se fields is not clear. I highly recommend setting all of <strong>the</strong>sefields to an appropriate value for any event you send into <strong>Zenoss</strong> to improve <strong>the</strong> ability of <strong>Zenoss</strong> and<strong>Zenoss</strong>’ operators to manage <strong>the</strong> events.• device: Mandatory. The device id related to <strong>the</strong> event.• component: Optional. The component id related to <strong>the</strong> event.• severity: Mandatory. The severity for <strong>the</strong> event.• eventKey: Optional. A fur<strong>the</strong>r uniqueness key for <strong>the</strong> event. Used for de-duplication and clearing.• eventClassKey: Optional. An identifier for <strong>the</strong> type of event. Used during event class mapping.• summary: Mandatory: A (hopefully) short summary of <strong>the</strong> event. Truncated to 128 characters.1.1. ZenPack Development Guide 56


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release• message: Optional: A longer text description of <strong>the</strong> event. Not truncated.You will also see many wu-* fields being added to <strong>the</strong> event. <strong>Zenoss</strong> allows arbitrary fields on events soit can be a good practice to add any fur<strong>the</strong>r information you get about <strong>the</strong> event in this way. It can makeunderstanding and troubleshooting <strong>the</strong> resulting event easier.Finally we return data with all of events we appended to it. zenpython will take care of getting <strong>the</strong> eventssent from this point.2. Restart <strong>Zenoss</strong>.After adding a new datasource plugin you must restart <strong>Zenoss</strong>. If you’re following <strong>the</strong> Running a Minimal<strong>Zenoss</strong> instructions you really only need to restart zenhub.That’s it. The datasource plugin has been created. Now we just need to do some <strong>Zenoss</strong> configuration to allow us touse it.Configure Monitoring TemplatesRa<strong>the</strong>r than use <strong>the</strong> web interface to manually Add a Monitoring Template, we’ll jump to <strong>the</strong> next section on UsingYAML Templates to show how we can describe a monitoring template using YAML.Using YAML TemplatesOrdinarily monitoring templates are built within <strong>the</strong> <strong>Zenoss</strong> web interface. If you want one of <strong>the</strong>se templates includedwith a ZenPack, you simply use <strong>the</strong> web interface to add <strong>the</strong> template to any of <strong>the</strong> ZenPacks you have in development.Sometimes you know that you have a lot of monitoring templates to create, or <strong>the</strong> monitoring templates have a lot ofdatasource, datapoints, thresholds and graphs. It can be tedious and error-prone to add all of <strong>the</strong>se through <strong>the</strong> webinterface. In <strong>the</strong>se cases it may be preferable to load monitoring templates from files.There’s a script available called load-templates that makes it possible to load monitoring templates from YAML sourcefiles.Download load-templatesRun <strong>the</strong> following commands to download <strong>the</strong> load-templates script into your ZenPack:cd $ZP_TOP_DIRwget https://raw.githubusercontent.com/zenoss/zenpacklib/master/load-templateschmod 755 load-templatesNote: Note that we’re putting this script into $ZP_TOP_DIR, not $ZP_DIR. This isn’t required, but it’s a goodpractice. Files located in $ZP_TOP_DIR won’t by default be included in your ZenPack once it’s built. Since loadtemplatesis only used by <strong>the</strong> ZenPack developer, it doesn’t need to be included in <strong>the</strong> built ZenPack.This script requires that <strong>the</strong> PyYAML library be installed on your system. You can run <strong>the</strong> following command as <strong>the</strong>zenoss user to install it:easy_install PyYAML1.1. ZenPack Development Guide 57


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseCreate templates.yamlWe now want to create <strong>the</strong> YAML file that can be used to create a monitoring template that will be bound to ourlocations and execute our Alerts data source plugin.Follow <strong>the</strong>se steps to create this monitoring template:1. Create $ZP_TOP_DIR/templates.yaml with <strong>the</strong> following contents./Wea<strong>the</strong>rUnderground/Location:description: Wea<strong>the</strong>r Underground location monitoring.targetPythonClass: ZenPacks.training.Wea<strong>the</strong>rUnderground.WundergroundLocationdatasources:alerts:type: Pythonplugin_classname: ZenPacks.training.Wea<strong>the</strong>rUnderground.dsplugins.Alertscycletime: "600"At least some of this should be self-explanatory. The YAML vocabulary has been designed to be as intuitive andconcise as possible. Let’s walk through it.(a) The highest-level element (based on indentation) is /Wea<strong>the</strong>rUnderground/Location. This means to createa Location monitoring template in <strong>the</strong> /Wea<strong>the</strong>rUnderground device class.Note: Because we’re using zenpacklib <strong>the</strong> monitoring template must be called Location because <strong>the</strong> is<strong>the</strong> label for <strong>the</strong> WundergroundLocation class to which we want <strong>the</strong> template bound.(b) The description is for documentation purposes and should describe <strong>the</strong> purpose of <strong>the</strong> monitoring template.(c) The targetPythonClass is a hint to what type of object <strong>the</strong> template is meant to be bound to. Currently thisis only used to determine if users should be allowed to manually bind <strong>the</strong> template to device classes ordevices. Providing a valid component type like we’ve done prevents users from making this mistake.(d) Next we have datasources with a single alerts datasource defined.The alerts datasource only has three properties:• type: This is what makes zenpython collect <strong>the</strong> data.• plugin_classname: This is <strong>the</strong> fully-qualified class name for <strong>the</strong> PythonDataSource plugin we createdthat will be responsible for collecting <strong>the</strong> datasource.• cycletime: The interval in seconds at which this datasource should be collected.2. Run <strong>the</strong> following commands to create <strong>the</strong> monitoring template defined in templates.yaml.cd $ZP_TOP_DIR./load-templates templates.yaml3. Navigate to Advanced -> Monitoring Templates in <strong>the</strong> web interface to verify that <strong>the</strong> Location monitoringtemplate has been created as defined.4. Export <strong>the</strong> ZenPack. (Exporting a ZenPack)The /Wea<strong>the</strong>rUnderground device class is already part of our ZenPack, and we put <strong>the</strong> Location monitoring templateinto that device class. So exporting causes <strong>the</strong> template to be dumped into <strong>the</strong> ZenPack’s objects.xmlfile.1.1. ZenPack Development Guide 58


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseTest Monitoring Wea<strong>the</strong>r AlertsTesting this is a bit tricky since we’ll have to be monitoring a location that currently has an active wea<strong>the</strong>r alert.Fortunately <strong>the</strong>re’s an easy way to find one of <strong>the</strong>se locations.Follow <strong>the</strong>se steps to test wea<strong>the</strong>r alert monitoring:1. Go to <strong>the</strong> following URL for <strong>the</strong> current severe wea<strong>the</strong>r map of <strong>the</strong> United States.http://www.wunderground.com/severe.asp2. Click on one of <strong>the</strong> colored areas. Orange and red are more exciting. This will take you to <strong>the</strong> text of <strong>the</strong>warning. It should reference city or county names.3. Update zWundergroundLocations on <strong>the</strong> wunderground.com device to add one of <strong>the</strong> cities or counties that hasan active wea<strong>the</strong>r alert. For example, “Buffalo, South Dakota”.4. Remodel <strong>the</strong> wunderground.com device <strong>the</strong>n verify that <strong>the</strong> new location is modeled.5. Run <strong>the</strong> following command to collect from wunderground.com.zenpython run -v10 --device=wunderground.comThere will be a lot of output from this command, but we’re mainly looking for an event to be sent for <strong>the</strong> wea<strong>the</strong>ralert. It will look similar to <strong>the</strong> following output:DEBUG zen.zenpython: Queued event (total of 1) {’rcvtime’: 1403112635.631883, ’wu-type’: u’FIR’,Python Collector Plugin (Data Points)We’ve already created a data source plugin that creates <strong>Zenoss</strong> events for wea<strong>the</strong>r alerts. Now we want to use <strong>the</strong>Wea<strong>the</strong>r Underground Conditions API to monitor current wea<strong>the</strong>r conditions for each location. The purpose of this isto illustrate that <strong>the</strong>se Python data source plugins can also be used to collect datapoints.Create Conditions Data Source PluginFollow <strong>the</strong>se steps to create <strong>the</strong> Conditions data source plugin:1. Add <strong>the</strong> following contents to <strong>the</strong> end of $ZP_DIR/dsplugins.py.Most of <strong>the</strong> Conditions plugin is almost identical to <strong>the</strong> Alerts plugin so I won’t repeat whatcan be read back in that section. The main difference starts at <strong>the</strong> current_observation =response[’current_observation’] line of <strong>the</strong> collect method.It grabs <strong>the</strong> current_observation data from <strong>the</strong> response <strong>the</strong>n iterates over every datapoint configured on <strong>the</strong>datasource. This is a nice approach because it allows for some user-flexibility in what datapoints are capturedfrom <strong>the</strong> Conditions API. If <strong>the</strong> API made temp_c and temp_f available, we could choose to collect temp_c justby adding a datapoint by that name.The following line is <strong>the</strong> most important in terms of explaining how to have your plugin return datapoint values.Basically we just stick (value, ’N’) into <strong>the</strong> component’s datapoint dictionary. The ’N’ is <strong>the</strong> timestampat which <strong>the</strong> value occurred. If you know <strong>the</strong> time it should be specified as <strong>the</strong> integer UNIX timestamp. Use’N’ if you don’t know. This will use <strong>the</strong> current time.2. Restart <strong>Zenoss</strong>.After adding a new datasource plugin you must restart <strong>Zenoss</strong>. If you’re following <strong>the</strong> Running a Minimal<strong>Zenoss</strong> instructions you really only need to restart zenhub.1.1. ZenPack Development Guide 59


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseThat’s it. The datasource plugin has been created. Now we just need to do some <strong>Zenoss</strong> configuration to allow us touse it.Add Conditions to Monitoring TemplateTo use this new plugin we’ll add a new datasource and corresponding graphs to <strong>the</strong> existing Location monitoringtemplate defined in templates.yaml.Follow <strong>the</strong>se steps to update <strong>the</strong> monitoring template:1. Update $ZP_TOP_DIR/templates.yaml with <strong>the</strong> following content. This includes what should alreadybe in <strong>the</strong> file./Wea<strong>the</strong>rUnderground/Location:description: Wea<strong>the</strong>r Underground location monitoring.targetPythonClass: ZenPacks.training.Wea<strong>the</strong>rUnderground.WundergroundLocationdatasources:alerts:type: Pythonplugin_classname: ZenPacks.training.Wea<strong>the</strong>rUnderground.dsplugins.Alertscycletime: "600"conditions:type: Pythonplugin_classname: ZenPacks.training.Wea<strong>the</strong>rUnderground.dsplugins.Conditionscycletime: "600"datapoints:temp_c: GAUGEfeelslike_c: GAUGEheat_index_c: GAUGEwindchill_c: GAUGEdewpoint_c: GAUGErelative_humidity: GAUGEpressure_mb: GAUGEprecip_1hr_metric: GAUGEUV: GAUGEwind_kph: GAUGEwind_gust_kph: GAUGEvisibility_km: GAUGEgraphs:Temperatures:units: degrees C.graphpoints:Temperature:dpName: conditions_temp_cformat: "%7.2lf"Feels Like:dpName: conditions_feelslike_cformat: "%7.2lf"Heat Index:dpName: conditions_heat_index_cformat: "%7.2lf"1.1. ZenPack Development Guide 60


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseWind Chill:dpName: conditions_windchilltemp_cformat: "%7.2lf"Dewpoint:dpName: conditions_dewpoint_cformat: "%7.2lf"Relative Humidity:units: percentminy: 0maxy: 100graphpoints:Relative Humidity:dpName: conditions_relative_humidityformat: "%7.2lf%%"Pressure:units: millibarsminy: 0graphpoints:Pressure:dpName: conditions_pressure_mbformat: "%7.0lf"Precipitation:units: centimetersminy: 0graphpoints:1 Hour:dpName: conditions_precip_1hr_metricformat: "%7.2lf"UV Index:units: UV indexminy: 0maxy: 12graphpoints:UV Index:dpName: conditions_UVformat: "%7.0lf"Wind Speed:units: kphminy: 0graphpoints:Sustained:dpName: conditions_wind_kphformat: "%7.2lf"Gust:dpName: conditions_wind_gust_kphformat: "%7.2lf"1.1. ZenPack Development Guide 61


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseVisibility:units: kilometersminy: 0graphpoints:Visibility:dpName: conditions_visibility_kmformat: "%7.2lf"Only <strong>the</strong> first 9 lines previously existed for <strong>the</strong> alerts support. Adding <strong>the</strong> conditions datasource with its 12datapoints and corresponding 9 graphs accounts for <strong>the</strong> remaining 104 lines.2. Run <strong>the</strong> following commands to create <strong>the</strong> monitoring template defined in templates.yaml.cd $ZP_TOP_DIR./load-templates templates.yaml3. Navigate to Advanced -> Monitoring Templates in <strong>the</strong> web interface to verify that <strong>the</strong> Location monitoringtemplate has been updated with <strong>the</strong> conditions datasource and corresponding graphs.4. Export <strong>the</strong> ZenPack. (Exporting a ZenPack)The /Wea<strong>the</strong>rUnderground device class is already part of our ZenPack, and we put <strong>the</strong> Location monitoring templateinto that device class. So exporting causes <strong>the</strong> template to be dumped into <strong>the</strong> ZenPack’s objects.xmlfile.Test Monitoring Wea<strong>the</strong>r ConditionsFollow <strong>the</strong>se steps to test wea<strong>the</strong>r condition monitoring:1. Run <strong>the</strong> following command to collect from wunderground.com.zenpython run -v10 --device=wunderground.comThere will be a lot of output from this command, but we’re mainly looking for at least one datapoint beingwritten. If one works, it’s likely that <strong>the</strong>y all work. Look for a line similar to <strong>the</strong> following:DEBUG zen.RRDUtil: /opt/zenoss/perf/Devices/wunderground.com/80901.1.99999/conditions_temp_c.rrdPython Collector Plugin (Modeling)The final capability of Python data source plugins is to make changes to <strong>the</strong> <strong>Zenoss</strong> model. This allows a data sourceto make changes to <strong>the</strong> model in <strong>the</strong> same way that zenmodeler does. Having this capability in a data source allowsmodeling more frequently than <strong>the</strong> normal 12 hour zenmodeler interval.To demonstrate this through an exercise, we’ll extend <strong>the</strong> existing Conditions plugin to capture <strong>the</strong> what <strong>the</strong> ConditionsAPI calls wea<strong>the</strong>r which is some text that looks like “Scattered Clouds” or “Sunny”. We’ll <strong>the</strong>n show this value foreach location in <strong>the</strong> web interface.AddAdd Modeling to Conditions Data Source PluginFollow <strong>the</strong>se steps to add modeling to <strong>the</strong> Conditions data source plugin:1.1. ZenPack Development Guide 62


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release1. Edit $ZP_DIR/__init__.py.Add <strong>the</strong> following wea<strong>the</strong>r property to <strong>the</strong> WundergroundLocation class between <strong>the</strong> existing timezone andapi_link properties.’wea<strong>the</strong>r’: {’label’: ’Wea<strong>the</strong>r’,’order’: 4.2,},2. Edit $ZP_DIR/dsplugins.py.Add <strong>the</strong> following needed import to <strong>the</strong> top of dsplugins.py.from Products.DataCollector.plugins.DataMaps import ObjectMapAdd <strong>the</strong> following code to <strong>the</strong> Conditions class’ collect method right above <strong>the</strong> returnValue(data) lineindented one level fur<strong>the</strong>r. The returnValue(data) line is included in <strong>the</strong> following update to show where<strong>the</strong> new code should be placed.data[’maps’].append(ObjectMap({’relname’: ’wundergroundLocations’,’modname’: ’ZenPacks.training.Wea<strong>the</strong>rUnderground.WundergroundLocation’,’id’: datasource.component,’wea<strong>the</strong>r’: current_observation[’wea<strong>the</strong>r’],}))returnValue(data)# existing lineThe maps concept here is exactly <strong>the</strong> same as it is in modeler plugins. data[’maps’] can contain anythingthat a modeler plugin’s process method can return.2. Don’t update <strong>the</strong> Location monitoring template.We’re adding capability to a datasource that’s already configured. No updates are required to <strong>the</strong> monitoringtemplate.3. Restart <strong>Zenoss</strong>.If we had only updated <strong>the</strong> collect method of <strong>the</strong> Conditions plugin we would only need to restart zenpython.However, because we added <strong>the</strong> new wea<strong>the</strong>r property to <strong>the</strong> WundergroundLocation class, we must restartnearly everything, so it’s simpler to restart everything.Test Modeling Current Wea<strong>the</strong>rFollow <strong>the</strong>se steps to test wea<strong>the</strong>r condition monitoring:1. Run <strong>the</strong> following command to collect from wunderground.com.zenpython run -v10 --device=wunderground.comThere will be a lot of output from this command, but we’re looking for <strong>the</strong> following line which indicates thatour maps were applied:DEBUG zen.python: Applying 1 datamaps to wunderground.com2. Navigate to <strong>the</strong> Locations on <strong>the</strong> wunderground.com device and verify that each location shows something in itsWea<strong>the</strong>r column.1.1. ZenPack Development Guide 63


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release1.1.7 Exporting a ZenPackNow that we’ve created a ZenPack and added some configuration to it, we need to export it. Exporting a ZenPack takesall of <strong>the</strong> object’s you’ve added to your ZenPack through <strong>the</strong> web interface and compiles <strong>the</strong>m into an objects.xmlfile that gets saved into your ZenPack’s source directory in <strong>the</strong> file system.Follow <strong>the</strong>se steps to export <strong>the</strong> NetBotz ZenPack.1. Navigate to Advanced -> ZenPacks -> NetBotz ZenPack in <strong>the</strong> web interface.2. Scroll to <strong>the</strong> bottom of <strong>the</strong> page to see what objects <strong>the</strong> ZenPack provides.All objects listed in <strong>the</strong> ZenPack Provides section and objects contained within <strong>the</strong>m will be exported.3. Choose Export ZenPack from <strong>the</strong> gear menu in <strong>the</strong> bottom-left of <strong>the</strong> screen.4. Choose to only export and not download <strong>the</strong>n click OK.You could also choose to download <strong>the</strong> ZenPack through your web browser. However, <strong>the</strong> downloaded file willbe <strong>the</strong> built egg distribution format of <strong>the</strong> ZenPack. This means that it can be installed into o<strong>the</strong>r <strong>Zenoss</strong> systems,but is not suitable for fur<strong>the</strong>r development.This will export everything under ZenPack Provides to a file within your ZenPack’s source called objects.xml. Noo<strong>the</strong>r files in your ZenPack’s source directory are created or modified. You can find this file in <strong>the</strong> following path:$ZENHOME/ZenPacks/ZenPacks.training.NetBotz/ZenPacks/yourname/NetBotz/objects/objects.xmlEach time you add a new object to you ZenPack within <strong>the</strong> web interface, or modify an object that’s already containedwithin your ZenPack, you should export <strong>the</strong> ZenPack again to update objects.xml. If you’re using version control onyour ZenPack’s source directory this would be a good time to commit <strong>the</strong> resulting change to objects.xml.Warning: Exporting a ZenPack completely overwrites <strong>the</strong> objects.xml file that previously existed within <strong>the</strong>ZenPack’s source directory. For this reason it is recommended that <strong>the</strong> objects.xml file never be modified by hand.1.1.8 TroubleshootingUsing <strong>the</strong> Python DebuggerOne of <strong>the</strong> most powerful tools when debugging <strong>the</strong> Python portions of a ZenPack is <strong>the</strong> Python debugger (pdb). Withpdb you can set breakpoints in your code. When <strong>the</strong> breakpoints are hit, you get a (pdb) prompt that has full access toexamine <strong>the</strong> stack and any local or global variables.To set a breakpoint in your code you add <strong>the</strong> following line:import pdb; pdb.set_trace()As with any code change, you must restart <strong>the</strong> <strong>Zenoss</strong> process that executes <strong>the</strong> code in question.1.2 ZenPack <strong>Documentation</strong>ZenPacks must be documented using reStructuredText. The minimum documentation requirement is that each Zen-Pack have a README.rst located in its top-level directory.An optional top-level docs/ directory containing at least one file named index.rst can also be created to suplement<strong>the</strong> README.rst. This would be <strong>the</strong> recommended approach if a ZenPack’s documentation requires <strong>the</strong>additional complexity of additional structure, files, or Sphinx extensions.1.2. ZenPack <strong>Documentation</strong> 64


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseContents:1.2.1 ZenPack Standards GuideThis document describes all requirements and recommendations for ZenPack development. The intended audience isall <strong>Zenoss</strong>, Inc. employees who create or modify ZenPacks including engineering, services and support. The intendedaudience also includes any third-parties that create or modify ZenPacks that are delivered to <strong>Zenoss</strong> customers by<strong>Zenoss</strong>, Inc.AssumptionsSome assumptions are made in this document regarding access to test facilities.Product InclusionZenPacks must always be developed with <strong>the</strong> understanding that <strong>the</strong> potential exists for <strong>the</strong>m to become part of <strong>the</strong>product. Even in cases were ownership of <strong>the</strong> ZenPack does not rest with <strong>Zenoss</strong> this must be observed becauseownership can change in <strong>the</strong> future. For this reason, special attention must be paid to document <strong>the</strong> inclusion of anysensitive company data in ZenPacks.Access to Test EnvironmentIn cases where <strong>the</strong> ZenPack developer(s) do not have access to endpoints necessary for integration and testing worksome standard operating procedures must be suspended.File LocationsThe location of specific files within a ZenPack’s directory structure is technically mandated in some circumstances,and open to <strong>the</strong> developer’s desires in o<strong>the</strong>rs. To make it easier for o<strong>the</strong>r developers to more easily get up to speedwith <strong>the</strong> ZenPack in <strong>the</strong> future, <strong>the</strong> following recommendations for file locations should be used.• ZenPacks./• ZenPacks./ZenPacks/namespace/PackName/– browser/* configure.zcml All ZCML definitions related to defining browser views or wiring browser-onlyconcerns. This configure.zcml should be included by <strong>the</strong> default configure.zcml in itsparent directory.* resources/· css/ - All stylesheets loaded by users’ browsers.· img/ - All images loaded by users’ browsers.· js/ - All javascript loaded by users’ browser.– lib/ Any third-party modules included with <strong>the</strong> ZenPack should be located in this directory. In <strong>the</strong> caseof pure-Python modules <strong>the</strong>y can be located directly here. In <strong>the</strong> case of binary modules <strong>the</strong> build processmust install <strong>the</strong>m here. See <strong>the</strong> section of License Compliance below for more information on how toproperly handle third-party content.– libexec/ Any scripts intended to be run by <strong>the</strong> zencommand daemon must be located in this directory.1.2. ZenPack <strong>Documentation</strong> 65


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release– objects/ There should only ever be a single file called objects.xml in this directory. While <strong>the</strong> ZenPackinstallation code will load objects from any file in this directory with a .xml extension, <strong>the</strong> ZenPack exportcode will dump all objects back to objects.xml so creating o<strong>the</strong>r files only creates future confusionbetween installation and export.– facades.py All facades (classes implementing Products.Zuul.interfaces.IFacade) shouldbe defined in this file. In ZenPacks where this single file becomes hard to maintain, a facades/ directoryshould be created containing individual files named for <strong>the</strong> group of facades <strong>the</strong>y contain.– info.py All info adapters (classes implementing Products.Zuul.interfaces.IInfo) shouldbe defined in this file. In ZenPacks where this single file becomes hard to maintain, an info/ directoryshould be created containing individual files named for <strong>the</strong> group of info adapters <strong>the</strong>y contain.– interfaces.py All interfaces (classes extending zope.interface.Interface) should be definedin this file. In ZenPacks where this single file becomes hard to maintain, an interfaces/ directoryshould be created containing individual files named for <strong>the</strong> group of interfaces <strong>the</strong>y contain.– routers.py All routers (classes extending Products.ZenUtils.Ext.DirectRouter) shouldbe defined in this file. In ZenPacks where this single file becomes hard to maintain, a routers/ directoryshould be created containing individual files named for <strong>the</strong> group of routers <strong>the</strong>y contain.License ComplianceAll ZenPack content must be compliant with <strong>the</strong> license of <strong>the</strong> ZenPack being developed. If you intend to include athird-party module with a GPL license, <strong>the</strong> ZenPack must also carry a GPL license and not include any o<strong>the</strong>r codethat would violate <strong>the</strong> GPL license. Always run third-party module inclusion through legal to make sure <strong>the</strong>re is noconflict.Coding StandardsAll code and configuration in ZenPacks should be developed according to <strong>the</strong> following public style guides.• Python– PEP 8 – Style Guide or Python Code– PEP 257 – <strong>Docs</strong>tring Conventions• ZCML– Zope’s ZCML Style GuideMonitoring Template StandardsPerformance templates are one of <strong>the</strong> easiest places to make a real user experience difference when new features areadded to <strong>Zenoss</strong>. Spending a very small amount of time to get <strong>the</strong> templates right goes a long way towards improving<strong>the</strong> overall user experience. For this reason, <strong>the</strong> following checklist should be used to determine if your monitoringtemplate is acceptable.Templates1. Is <strong>the</strong> template worthwhile? Should it be removed?2. Is <strong>the</strong> template at <strong>the</strong> correct point in <strong>the</strong> model?3. Does <strong>the</strong> template have a description? Is <strong>the</strong> description a good one?1.2. ZenPack <strong>Documentation</strong> 66


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseData Sources1. Can your datasource be named better?1. Is it a common metric that is being collected from o<strong>the</strong>r devices in ano<strong>the</strong>r way? If so, name yours <strong>the</strong> same.This makes global reporting much easier.2. camelCaseNames seem to be <strong>the</strong> standard. Use <strong>the</strong>m.2. Never use absolute paths for COMMAND datasource command templates. This will end up causing problemson one of <strong>the</strong> three platforms we deal with. Link your plugin into zenPath(‘libexec’) instead.Data Points1. Using a COUNTER? You might want to think o<strong>the</strong>rwise.1. Unnoticed counter rollovers can result in extremely skewed data.2. Using a DERIVE with a minimum of 0 will record unknown instead of wrong data.2. Enter <strong>the</strong> minimum and/or maximum possible values for <strong>the</strong> data point if you know <strong>the</strong>m.1. This again will allow unknown to be recorded instead of bad data.Data Point Aliases1. Include <strong>the</strong> unit in <strong>the</strong> alias name if it is in any way not obvious. For example, use cpu_percent instead ofcpu_usage.2. Use an RPN to calculate <strong>the</strong> base unit if <strong>the</strong> data point isn’t already collected that way. For example, use1024,* to convert a data point collected in KBytes to bytes.Thresholds1. Don’t include a number in your threshold’s name.1. This makes people have to recreate <strong>the</strong> threshold if <strong>the</strong>y want to change it.Graph Definitions1. Have you entered <strong>the</strong> units? Do it!1. This will become <strong>the</strong> y-axis label and should be all lowercase.2. Always use <strong>the</strong> base units. Never kbps or MBs. bps or bytes are better.2. Do you know <strong>the</strong> minimum/maximum allowable values? Enter <strong>the</strong>m!1. Common scenarios include percentage graphing with minimum 0 and maximum 100.3. Think about <strong>the</strong> order of your graph points. Does it make sense?4. Are <strong>the</strong>re o<strong>the</strong>r templates that show similar data to yours?1. If so, you should try hard to mimic <strong>the</strong>ir appearance to create a consistent experience.1.2. ZenPack <strong>Documentation</strong> 67


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseGraph Points1. Have you changed <strong>the</strong> legend? Do it!2. Adjust <strong>the</strong> format so that it makes sense.1. %5.2lf%s is good for values you want RRDTool to auto-scale.2. %6.2lf%% is good for percentages.3. %4.0lf is good for four digit numbers with no decimal precision or scaling.3. Should you be using areas or lines?1. Lines are good for most values.2. Areas are good for things that can be thought of as a volume or quantity.4. Does stacking <strong>the</strong> values to present a visual aggregate makes sense?ETL StandardsETL is an acronym for Extract, Transform, Load. When writing ETL adapters you’re defining how <strong>Zenoss</strong> modeldata is extracted and transformed into <strong>the</strong> <strong>Zenoss</strong> Analytics schema. The following guidelines should be used to keepreporting consistent.1. The reportProperties implementation in IReportable adapters must include <strong>the</strong> units in <strong>the</strong> name ifnot immediately obvious. For example, use cpu_used_percent instead of cpu_used.<strong>Documentation</strong>ZenPacks must be documented according to <strong>the</strong> ZenPack.example.Name template. The Zen-Packs.zenoss.SolarisMonitor documentation can be used as an example of a ZenPack that has been documented usingthis template.Code <strong>Documentation</strong>Python code must be documented in docstrings in <strong>the</strong> locations specified in PEP-8 and according to <strong>the</strong> style of PEP-257. Links to <strong>the</strong>se standards can be found in <strong>the</strong> Coding Standards section. Inline code comments should also beused when <strong>the</strong> code isn’t obvious.TestingThe following types of testing must be performed. All test results should be recorded in <strong>the</strong> ZenPack’s test resultmatrix. The matrix will have <strong>the</strong> ZenPack version on one axis and <strong>the</strong> <strong>Zenoss</strong> version on <strong>the</strong> o<strong>the</strong>r axis. At <strong>the</strong>intersection will be <strong>the</strong> result of unit testing, internal integration testing and live integration testing.Unit TestsUnit tests must be written for all public interfaces of ZenPack-specific code. Unit tests will be <strong>the</strong> only mechanism forautomated regression testing in some cases, and <strong>the</strong> primary source in all o<strong>the</strong>rs.1.2. ZenPack <strong>Documentation</strong> 68


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseInternal Integration TestingZenPacks must be tested internally using <strong>the</strong> packaged .egg that is will be delivered to <strong>the</strong> customer. The test servermust be <strong>the</strong> exact same version of <strong>Zenoss</strong> being used by <strong>the</strong> customer. The test environment must match <strong>the</strong> customer’senvironment as closely as possible. The only exception to internal integration testing is cases where it is not possibleto replicate <strong>the</strong> test environment internally.Live Integration TestingZenPacks must be tested in <strong>the</strong>ir live deployment environment. A development or staging instance of <strong>Zenoss</strong> thatmatches <strong>the</strong> production environment as closely as possible should be used.VersioningThe first feature-complete ZenPack delivered to a customer should be version 1.0.0. Subsequent versions must increment<strong>the</strong> micro version if <strong>the</strong>y contain only bugfixes or tweaks (i.e. 1.0.1.) Subsequent versions must increment <strong>the</strong>minor version if <strong>the</strong> contain new features (i.e. 1.1.0.)A ZenPack’s version must be incremented each time it is delivered to a customer if <strong>the</strong>re has been any change to itwhatsoever.ReviewsPeer review is a strong mechanism to catch potential issues before integration testing is performed. To that end <strong>the</strong>following reviews must be performed.Design ReviewThe initial design of a ZenPack must be peer reviewed before coding begins.Code ReviewAll code, including updates, must be peer reviewed before being committed to <strong>the</strong> mainline development branch orany stable release branch.Packaging & DeliveryAll ZenPacks must be delivered in <strong>the</strong>ir packaged .egg format. If arrangements have been made for <strong>the</strong> customer toalso get <strong>the</strong> source for <strong>the</strong> ZenPack it should be provided in addition to <strong>the</strong> packaged egg as a tarball of <strong>the</strong> developmentdirectory.ZenPacks must be built using <strong>the</strong> same environment that <strong>the</strong> customer will be installing <strong>the</strong>m into. If <strong>the</strong> customer isinstalling into multiple environments a separate egg should be built and delivered for each environment. In this context<strong>the</strong> same environment is defined as <strong>the</strong> following.• Exact same version of <strong>Zenoss</strong>• Same major version of operating system• Same architecture (i.e. i386 or x86_64)All files including documentation must be delivered to customers in a Parature ticket.1.2. ZenPack <strong>Documentation</strong> 69


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release1.2.2 ZenPack.example.NameAboutIntroduction to <strong>the</strong> ZenPack and any potentially foreign concepts such as <strong>the</strong> target that is being monitored or integratedwith.FeaturesHigh-level overview of <strong>the</strong> ZenPack’s features and functionality. Focus on <strong>the</strong> value proposition. Target audiencewould be people trying to understand <strong>Zenoss</strong>’ capabilities, not <strong>Zenoss</strong> administrators.PrerequisitesRequirements and dependencies that must be satisfied. This section should cover <strong>the</strong> following at a minimum andwhere applicable.• Minimum required <strong>Zenoss</strong> version• Maximum supported <strong>Zenoss</strong> version if known• ZenPack dependencies (with specific versions if known)• O<strong>the</strong>r installation dependencies if known (e.g. operating system packages)• Supported versions of <strong>the</strong> monitoring or integration targetLimitationsNote any shortcomings that <strong>the</strong> user might o<strong>the</strong>rwise be left curious about. This section is optional.UsageThe target audience for <strong>the</strong> entire Usage section is a <strong>Zenoss</strong> administrator.InstallingStandard installation steps plus any o<strong>the</strong>r installation steps or notes specific to this ZenPack.UsingOne or more ad-hoc usage related sections. This (or <strong>the</strong>se) sections will likely contain <strong>the</strong> bulk of <strong>the</strong> ZenPack’scustom documentation. The section(s) will not necessarily be called Using.RemovingStandard ZenPack removal steps plus any removal steps or notes specific to this ZenPack. Be especially careful tocover anything that will result in data loss such as removal of device classes and <strong>the</strong>ir contained devices.1.2. ZenPack <strong>Documentation</strong> 70


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseTroubleshootingDocument common problems users of <strong>the</strong> ZenPack may run into such as what happens in <strong>the</strong> result of au<strong>the</strong>nticationfailures or o<strong>the</strong>r configuration mistakes.AppendixThe two examples appendixes below will very commonly be used. Additional reference material can be made availablein additional appendixes. The Appendixes section can only be omitted if <strong>the</strong> ZenPack installs no items as describedbelow, and requires no non-platform daemons as described below.Appendex A: Installed ItemsDetail <strong>the</strong> items installed by <strong>the</strong> ZenPack. Items include <strong>the</strong> following.• Device Classes• Configuration Properties• Modeler Plugins• Command Parsers• Monitoring Templates• Process Classes• IP Service Classes• Windows Service Classes• Event Classes• Event Mappings• MIBs• ReportsAppendex B: Related DaemonsDetail <strong>the</strong> daemons outside of <strong>the</strong> core platform required to take advantage of all of <strong>the</strong> ZenPack’s functionality. Thecore platform daemons listed below should not be explicitly listed.• zeoctl• zeneventserver• zeneventd• zenhub• zenjobs• zendisc• zenmodeler• zenimpactserver• zenimpactgraph1.2. ZenPack <strong>Documentation</strong> 71


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release• zenimpactstate• zenjserverDaemons such as <strong>the</strong> following, plus any daemons delivered with <strong>the</strong> ZenPack should be listed.• zencommand• zenperfsnmp• zenprocess1.2.3 ZenPacks.zenoss.SolarisMonitorAboutThe SolarisMonitor ZenPack enables Resource Manager to use Secure Shell (SSH) to monitor Solaris hosts. ResourceManager models and monitors devices placed in <strong>the</strong> /Server/SSH/Solaris device class by running commands andparsing <strong>the</strong> output. Parsing of command output is performed on <strong>the</strong> Resource Manager server (if using a local collector)or on a distributed collector. The account used to monitor <strong>the</strong> device does not require root access or special privileges.In addition to <strong>the</strong> previously described modeling and monitoring features this ZenPack also enables Resource Managerto model and monitor Sun Solaris LDOM servers. Resource Manager will model devices utilizing <strong>the</strong> Simple NetworkManagement Protocol (SNMP) to collect LDOM information when a divice resides in ei<strong>the</strong>r <strong>the</strong> /Server/Solaris or/Server/SSH/Solaris device classes. The discovered LDOM information will be displayed as components of <strong>the</strong> LDOMhost server.FeaturesThe SolarisMonitor ZenPack provides:• File system and process monitoring• Network interfaces and route modeling• CPU utilization information• Hardware information (memory, number of CPUs, and model numbers)• OS information (OS-level, command-style information)• Pkginfo information (such as installed software)• LDOM monitoringPrerequisitesPrerequisite Restriction<strong>Zenoss</strong> Platform 3.1 or greaterInstalled ZenPacks ZenPacks.zenoss.SolarisMonitorFirewall Acccess Collector server to 22/tcp and 161/udp of Solaris serverSolaris Releases OpenSolaris 5.11, Solaris 9 and 10LimitationsThe SolarisMonitor ZenPack does not support monitoring in Solaris Zones or systems containing Solaris Zones. (Implementedwith Solaris 10, Solaris Zones act as isolated virtual servers within a single operating system instance.)1.2. ZenPack <strong>Documentation</strong> 72


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseUsageInstallationThis ZenPack has no special installation considerations. Depending on <strong>the</strong> version of <strong>Zenoss</strong> you’re installing <strong>the</strong>ZenPack into, you will need to verify that you have <strong>the</strong> correct package (.egg) to install.• <strong>Zenoss</strong> 4.1 and later: The ZenPack file must end with -py2.7.egg.• <strong>Zenoss</strong> 3.0 - 4.0: The ZenPack file must end with -py2.6.egg.To install <strong>the</strong> ZenPack you must copy <strong>the</strong> .egg file to your <strong>Zenoss</strong> master server and run <strong>the</strong> following command as<strong>the</strong> zenoss user:zenpack --install After installing you must restart <strong>Zenoss</strong> by running <strong>the</strong> following command as <strong>the</strong> zenoss user on your master<strong>Zenoss</strong> server:zenoss restartIf you have distributed collectors you must also update <strong>the</strong>m after installing <strong>the</strong> ZenPack.ConfiguringDepending on <strong>the</strong> version of Solaris you may be able to monitor <strong>the</strong> server using ei<strong>the</strong>r SSH or SNMP. For OpenSolarisand Solaris 10, you can choose to use ei<strong>the</strong>r SSH or SNMP monitoring. For Solaris 9, only SSH monitoring issupported.Configuring SSH Monitoring Use <strong>the</strong> following steps to configure <strong>Zenoss</strong> to monitor your Solaris server(s) usingSSH.1. Navigate to <strong>the</strong> /Server/SSH/Solaris device class’ configuration properties.2. Verify that <strong>the</strong> zCommandUsername and zCommandPassword are set to valid login credentials.3. Add your Solaris server(s) to <strong>the</strong> /Server/SSH/Solaris device class.Configuring SNMP Monitoring Use <strong>the</strong> following steps to configure <strong>Zenoss</strong> to monitor your Solaris server(s) usingSNMP.1. Verify that <strong>the</strong> snmpd process is running on your Solaris server(s).2. Navigate to <strong>the</strong> /Server/Solaris device class’ configuration properties.3. Verify that your Solaris server(s) SNMP community strings are listed in <strong>the</strong> zSnmpCommunities property.4. Add your Solaris server(s) to <strong>the</strong> /Server/Solaris device class.Configuring LDOM Monitoring For OpenSolaris and Solaris 10 servers you will also get support for monitoringLDOMs if <strong>the</strong>y’re used on <strong>the</strong> server. However, this monitoring is always performed using SNMP. If you’re alreadymonitoring your Solaris server using SNMP <strong>the</strong>re is no additional configuration required to monitor its LDOMs. If youconfigured <strong>Zenoss</strong> to monitor your Solaris server using SSH you should take <strong>the</strong> following steps to monitor LDOMs.1. Verify that <strong>the</strong> snmpd process is running on your Solaris server(s).2. Navigate to <strong>the</strong> /Server/SSH/Solaris device class’ configuration properties.3. Verify that your Solaris server(s) SNMP community strings are listed in <strong>the</strong> zSnmpCommunities property.1.2. ZenPack <strong>Documentation</strong> 73


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release4. Remodel your Solaris server(s) if <strong>the</strong>y’re already in <strong>the</strong> system. O<strong>the</strong>rwise add <strong>the</strong>m to <strong>the</strong>/Server/SSH/Solaris device class.RemovalUse caution when removing this ZenPack• Will permanently remove devices located in /Server/SSH/Solaris device class.• Will permanently remove LDOM modeled components for devices located in /Server/Solaris.• Will permanently remove associated monitored data for LDOM components.• Will permanently remove <strong>the</strong> /Server/SSH/Solaris device class.To remove this ZenPack you must run <strong>the</strong> following command as <strong>the</strong> zenoss user on your master <strong>Zenoss</strong> server:zenpack --remove ZenPacks.zenoss.SolarisMonitorYou must <strong>the</strong>n restart <strong>the</strong> master <strong>Zenoss</strong> server by running <strong>the</strong> following command as <strong>the</strong> zenoss user:zenoss restartTroubleshootingResolving CHANNEL_OPEN_FAILURE Issues The zencommand daemon’s log file($ZENHOME/collector/zencommand.log) may show messages stating:ERROR zen.SshClient CHANNEL_OPEN_FAILURE: Au<strong>the</strong>ntication failure WARNING:zen.SshClient:Open of commanIf <strong>the</strong> sshd daemon’s log file on <strong>the</strong> remote device is examined, it may report that <strong>the</strong> MAX_SESSIONS numberof connections has been exceeded and that it is denying <strong>the</strong> connection request. In <strong>the</strong> OpenSSH daemons, thisMAX_SESSIONS number is a compile-time option and cannot be reset in a configuration file.To work around this sshd daemon limitation, use <strong>the</strong> configuration property zSshConcurrentSessions to control <strong>the</strong>number of connections created by zencommand to <strong>the</strong> remote device:1. Navigate to <strong>the</strong> device or device class in <strong>the</strong> Resource Manager interface.• If applying changes to a device class:(a) Select <strong>the</strong> class in <strong>the</strong> devices hierarchy.(b) Click Details.(c) Select Configuration Properties.• If applying changes to a device:(a) Click <strong>the</strong> device in <strong>the</strong> device list.(b) Select Configuration Properties.2. Set <strong>the</strong> zSshConcurrentSessions property. Try 10 first, and 2 if that doesn’t resolve <strong>the</strong> problem.Resolving Command Timeout Issues The zencommand daemon’s log file ($ZEN-HOME/collector/zencommand.log) may show messages stating:WARNING:zen.zencommand:Command timed out on device device_name: command1.2. ZenPack <strong>Documentation</strong> 74


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseIf this occurs, it usually indicates that <strong>the</strong> remote device has taken too long to return results from <strong>the</strong> commands.To increase <strong>the</strong> amount of time to allow devices to return results, change <strong>the</strong> configuration propertyzCommandCommandTimeout to a larger value.1. Navigate to <strong>the</strong> device or device class in <strong>the</strong> Resource Manager interface.• If applying changes to a device class:(a) Select <strong>the</strong> class in <strong>the</strong> devices hierarchy.(b) Click Details.(c) Select Configuration Properties.• If applying changes to a device:(a) Click <strong>the</strong> device in <strong>the</strong> device list.(b) Select Configuration Properties.2. Increase <strong>the</strong> zCommandCommandTimeout property incrementally to a maximum of 240 until <strong>the</strong> timeout isresolved.AppendixesAppendix A: Installed ItemsType Name LocationDevice Class /SSH/Solaris /Devices/ServerModeler Plugin df_ag zenoss.cmd.solarisModeler Plugin kstat zenoss.cmd.solarisModeler Plugin memory zenoss.cmd.solarisModeler Plugin netstat_an zenoss.cmd.solarisModeler Plugin netstat_r_vn zenoss.cmd.solarisModeler Plugin pkginfo zenoss.cmd.solarisModeler Plugin process zenoss.cmd.solarisModeler Plugin uname_a zenoss.cmd.solarisModeler Plugin hostid zenoss.snmp.solarisModeler Plugin ldommap zenoss.snmp.solarisMonitoring Template Device /Server/SSH/SolarisMonitoring Template FileSystem /Server/SSH/SolarisMonitoring Template OSProcess /Server/SSH/SolarisMonitoring Template e<strong>the</strong>rnetCsmacd /Server/SSH/SolarisMonitoring Template LDOM /ServerMonitoring Template LDOMVcpu /ServerMonitoring Template LDOMVds /ServerEvent Class /Status/LDOM /Event Class /Status/LDOM/vCPU /Event Mapping ldomStateChange /ChangeEvent Mapping ldomVCpuChange /ChangeEvent Mapping ldomVccChange /ChangeEvent Mapping ldomVconsChange /ChangeEvent Mapping ldomVdiskChange /ChangeEvent Mapping ldomVdsChange /ChangeEvent Mapping ldomVmemChange /ChangeEvent Mapping ldomVnetChange /ChangeContinued on next page1.2. ZenPack <strong>Documentation</strong> 75


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseTable 1.1 – continued from previous pageType Name LocationEvent Mapping ldomVswChange /ChangeEvent Mapping ldomCreate /Change/AddEvent Mapping ldomDestroy /Change/RemoveMIB SUN-LDOM-MIB /Monitoring Templates Device (/Server/SSH/Solaris)• Data Points– cpu_ssCpuIdle– cpu_ssCpuInterrupt– cpu_ssCpuSystem– cpu_ssCpuUser– io_read– io_written– percent_memory_percentMemUsed– percent_swap_percentSwapUsed– uptime_laLoadInt1– uptime_laLoadInt5– uptime_laLoadInt15– uptime_sysUpTime• Thresholds– CPU Utilization– high load• Graphs– Load Average– CPU Utilization– Memory Utilization– IOFileSystem (/Server/SSH/Solaris)• Data Points– disk_availBlocks– disk_availNodes– disk_percentInodesUsed– disk_totalBlocks– disk_totalInodes– disk_usedBlocks– disk_usedInodes1.2. ZenPack <strong>Documentation</strong> 76


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release• Thresholds– high_disk_usage• Graphs– Utilization– Inode UtilizationOSProcess (/Server/SSH/Solaris) - Data Points• ps_count• ps_cpu• ps_mem• Graphs– CPU Utilization– Memory– Process Counte<strong>the</strong>rnetCsmacd (/Server/SSH/Solaris) - Data Points• intf_ifInErrors• intf_ifInPackets• intf_ifOutErrors• intf_ifOutPackets• intf_octets_ifInOctets• intf_octets_ifOutOctets• Thresholds– Utilization 75 perc• Graphs– Throughput– PacketsLDOM (/Server)• Data Sources– ldomOperState• Thresholds– operational stateLDOMVcpu (/Server)• Data Sources– ldomVcpuOperationalStatus– ldomVcpuUtilPercent• Threshold– operational status1.2. ZenPack <strong>Documentation</strong> 77


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release• Graph Definition– CPU UtilizationLDOMVds (/Server)• Data Source– ldomVdsNumofAvailVolume– ldomVdsNumofUsedVolume• Graph Definition– VolumesAppendex B: Required DaemonsIn addition to <strong>the</strong> core platform daemons <strong>the</strong> following optional daemons are required for this ZenPack to fully function.• zenperfsnmp• zencommand1.3 ZenPack TaxonomyZenPacks can be used to override almost any functionality of <strong>the</strong> <strong>Zenoss</strong> platform, or to add any new features. Thiswide-open extensibility is much like Firefox’s add-on system and has <strong>the</strong> natural result of people building somesurprising things. For this reason it can be difficult to answer <strong>the</strong> question, “What is a ZenPack?”This document will outline <strong>the</strong> various ways ZenPacks can be classified to make it easier to find <strong>the</strong> ZenPack you need,describe a ZenPack that already exists, and to serve as an example for what is possible.1.3.1 ZenPack ClassificationsEvery ZenPack will be classified using one or more of <strong>the</strong> elements listed under each classifier property. Some classifierproperties have mutually exclusive elements, and some do not. This distinction will be made in <strong>the</strong> definition of eachclassifier property. For examples of how some existing representative ZenPacks are classified see Example ZenPackClassifications.FunctionalityFunctionality classifies <strong>the</strong> high-level type of feature(s) provided. Specifically its type of interaction with <strong>the</strong> environmentoutside of <strong>the</strong> <strong>Zenoss</strong> platform. It is highly recommended that <strong>the</strong>se categories be treated as mutually exclusive.While it is possible for a ZenPack to build a ZenPack that delivers functionality in more than one of <strong>the</strong> followingcategories, this can lead to less modularity and confusion about what a ZenPack does.1.3. ZenPack Taxonomy 78


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseMonitoringMonitoring is one or more of status, event and performance collection. The collection can be through active polling,passive receiving or both. A monitoring ZenPack provides functionality to perform this collection for a specific targettechnology.Example ZenPacks.zenoss.ApacheMonitorIntegrationIntegration is defined as being any interaction with systems outside of <strong>Zenoss</strong> not deemed to be a Monitoring interaction.Examples include pushing or pulling non-monitoring data to or from an external system, or causing action in aremote system or allowing a remote system to cause action within <strong>Zenoss</strong>.Example ZenPacks.zenoss.RANCIDIntegratorPlatform ExtensionA <strong>Zenoss</strong> platform extension is defined as any functionality that doesn’t interact with outside systems. The providedfunctionality is instead used directly by users or by o<strong>the</strong>r parts of <strong>the</strong> <strong>Zenoss</strong> platform, or by o<strong>the</strong>r ZenPacks.Example ZenPacks.zenoss.DistributedCollectorMaintainerThe maintainer of a ZenPack is <strong>the</strong> organization or individual that controls <strong>the</strong> code repository for a ZenPack and is <strong>the</strong>gate for all changes including defect and enhancement resolution. The following categories are mutually exclusive.<strong>Zenoss</strong> EngineeringMaintained by <strong>the</strong> product engineering organization at <strong>Zenoss</strong>, Inc. Ongoing support is provided by <strong>Zenoss</strong>, Inc. at<strong>the</strong> same level as <strong>the</strong> <strong>Zenoss</strong> platform software for customers with an active subscription.Example ZenPacks.zenoss.Impact<strong>Zenoss</strong> ServicesMaintained by <strong>the</strong> services organization at <strong>Zenoss</strong>, Inc. Ongoing support is provided under a statement of work.Example ZenPacks.zenoss.ServiceNowIntegrator<strong>Zenoss</strong> CommunityMaintained by a member of <strong>the</strong> <strong>Zenoss</strong> community. Ongoing support is subject to <strong>the</strong> individual maintainers’ control,and is typically provided in <strong>the</strong> community web forums, mailing lists and IRC channel.Example ZenPacks.community.ZenODBC1.3. ZenPack Taxonomy 79


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseAvailabilityWho has access, license and permission to use <strong>the</strong> ZenPack. The following elements are mutually exclusive.Open SourceZenPack source and packages are available as free open source. Designed to function properly on a <strong>Zenoss</strong> systemwith or without commercial-only ZenPacks installed.Example ZenPacks.zenoss.ApacheMonitorAvailable with <strong>Zenoss</strong> SubscriptionZenPack packages are available at no extra cost to anyone with a <strong>Zenoss</strong> subscription, but are not installed by default.May have dependencies on Open Source ZenPacks or o<strong>the</strong>r ZenPacks that are Available with <strong>Zenoss</strong> Subscription.Example ZenPacks.zenoss.DatabaseMonitorAdditional Cost with <strong>Zenoss</strong> SubscriptionZenPack packages are available at an additional cost on top of an existing <strong>Zenoss</strong> subscription. May have dependencieson Open Source ZenPacks, ZenPacks that are Available with <strong>Zenoss</strong> Subscription, or o<strong>the</strong>r ZenPacks that areAdditional Cost with <strong>Zenoss</strong> Subscription.Example ZenPacks.zenoss.ImpactQA LevelThe level of automated, manual and field testing A ZenPack has. The elements are mutually exclusive.UntestedInsufficient automated testing to qualify as Automatically Tested, and insufficient manual testing to qualify as Q.A.Tested.Example ZenPacks.zenoss.OpenStackAutomatically TestedStandard automated testing passes plus a minimum of 90% unit test code coverage with all tests passing.Q.A. TestedTested, and passed, by <strong>the</strong> quality assurance group of <strong>Zenoss</strong>, Inc.1.3. ZenPack Taxonomy 80


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseComplexityDefined by <strong>the</strong> technical difficulty of implementing specific types of functionality within <strong>the</strong> ZenPack. The elementsare not mutually exclusive, and most ZenPacks will implement multiple types of functionality as defined below. Arough total complexity score could be created for each ZenPack by summing <strong>the</strong> complexity score of all implementedelements.ConfigurationBuilt entirely in <strong>the</strong> web interface. No programming knowledge required.Complexity 1Skills <strong>Zenoss</strong>Example ZenPacks.zenoss.IISMonitorScriptsScripts can be written in any language and do anything. Since all <strong>Zenoss</strong> customizations should be packaged asZenPacks, <strong>the</strong>y’re only included in ZenPacks as a packaging mechanism. They might not have any direct interactionwith <strong>the</strong> <strong>Zenoss</strong> platform.Complexity 2Skills Scripting (Any Language)Example ZenPacks.zenoss.RANCIDIntegratorCommand DataSource PluginsCommand datasource plugins can be written in any language and executed ei<strong>the</strong>r on <strong>the</strong> <strong>Zenoss</strong> server, or remotelyusing SSH. Without writing a custom parser (see next item) <strong>the</strong>y must write to STDOUT using ei<strong>the</strong>r <strong>the</strong> Nagios orCacti output formats and exit using <strong>the</strong> appropriate Nagios or cacti exit code.Complexity 2Skills Scripting (Any Language)Example ZenPacks.zenoss.ApacheMonitorEvent Class Transforms and MappingsBuilt in <strong>the</strong> web interface. Basic Python knowledge required.Complexity 2Skills <strong>Zenoss</strong>, Basic PythonExample ZenPacks.zenoss.OpenStack1.3. ZenPack Taxonomy 81


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseCommand DataSource ParsersCommand datasource parsers must be written in Python and conform to <strong>the</strong> <strong>Zenoss</strong> CommandParser API. Theseparsers must be written to extract extended data from <strong>the</strong> output of command datasource plugins (see previous item),or to handle output that doesn’t conform to <strong>the</strong> Nagios or Cacti output formats.Complexity 3Skills <strong>Zenoss</strong>, PythonExample ZenPacks.zenoss.SolarisMonitorDataSource TypesWhen a new datasource is added in <strong>the</strong> web interface you must choose <strong>the</strong> type. Creating a DataSource type in aZenPack is a way to add new types to this list. The ApacheMonitor ZenPack listed as <strong>the</strong> example below adds <strong>the</strong>ability to collect performance metrics from an Apache httpd server using mod_status.New DataSource types are written in Python and must subclass RRDDataSource or one of its existing subclasses.Additionally an API adapter must also be written in Python to define <strong>the</strong> user interface to <strong>the</strong> datasource properties.Complexity 4Skills <strong>Zenoss</strong>, ZCML, PythonExample ZenPacks.zenoss.ApacheMonitorImpact AdaptersThere are three types of impact adapters. All are written in Python and added to <strong>the</strong> system configuration throughZCML directives.The first is a state provider. These implement <strong>the</strong> IStateProvider interface and allow manipulation of how agiven node type’s state within <strong>the</strong> impact graph is calculated.The second is a relations provider. These implement <strong>the</strong> IRelationshipDataProvider interface and allowmanipulation of what o<strong>the</strong>r nodes a given node type impacts, and what o<strong>the</strong>r nodes impact it.The third is a triggers provider. These implement <strong>the</strong> INodeTriggers interface and allow manipulation of <strong>the</strong>default impact policies set on a given type of node.Complexity 5Skills <strong>Zenoss</strong>, ZCML, PythonExample ZenPacks.zenoss.ZenVMwareETL AdaptersETL is used to export model, performance and event data from a <strong>Zenoss</strong> instance to a <strong>Zenoss</strong> Analytics instance.However, ETL adapters only need to be written to manipulate <strong>the</strong> model data that is exported. There are two types ofETL adapters. They’re both written in Python and added to <strong>the</strong> system configuration through ZCML directives.The first type is a reportable. These implement <strong>the</strong> IReportable interface and allow precise control over whichproperties of an object type are exported, and how <strong>the</strong>y’re named and manipulated for export.The second type is a reportable factory. These implement <strong>the</strong> IReportableFactory interface and all manipulationof which objects are considered for export. By default all devices and components are considered for extraction so1.3. ZenPack Taxonomy 82


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Releasea reportable factory is usually only used when fine-grained control over <strong>the</strong> relationships between <strong>the</strong>se objects isneeded.Complexity 4Skills <strong>Zenoss</strong>, ZCML, PythonExample ZenPacks.zenoss.ZenVMwareUser InterfaceModifications to <strong>the</strong> existing user interface, or entirely new sections of user interface. The difficulty of <strong>the</strong>se changesvaries considerably. See <strong>the</strong> Skills field below for <strong>the</strong> range of skills that could be required to make <strong>the</strong>se kinds ofchanges.The ServiceNowIntegrator example given below adds a new button to <strong>the</strong> event console that pops up a new dialog boxwith some custom options available. Only ZCML and JavaScript were required for this type of change.TAL is usually only required when editing or creating old-style pages that aren’t entirely built using ExtJS.Complexity 5Skills <strong>Zenoss</strong>, ZCML, TAL, JavaScript, ExtJSExample ZenPacks.zenoss.ServiceNowIntegratorModeler Plugins - SNMP, COMMAND, WMIModeler plugins provide <strong>the</strong> mapping between data collected from <strong>the</strong> environment and <strong>the</strong> <strong>Zenoss</strong> model. In <strong>the</strong>case where <strong>the</strong> data can be collected using SNMP, COMMAND (run a command remotely via SSH) or WMI, <strong>the</strong>re isexisting infrastructure to make <strong>the</strong>se tasks easier. However, <strong>the</strong> modeler plugins are still written in Python.If collecting using SNMP <strong>the</strong> SnmpPlugin class can be extended to do <strong>the</strong> hard parts of SNMP gets or walks for you.If collecting by running a command on a remote system via SSH, <strong>the</strong> CommandPlugin class can be extended to do<strong>the</strong> hard parts of SSH and output parsing for you. If collecting from a Windows system using WMI, <strong>the</strong> WmiPluginclass can be extended to do <strong>the</strong> hard parts of WQL querying for you.The only significant logic that must be implemented in <strong>the</strong>se cases is turning <strong>the</strong> returned data structures intoObjectMap and RelationshipMap objects to apply to <strong>the</strong> <strong>Zenoss</strong> model.Complexity 6Skills <strong>Zenoss</strong>, Python, (SNMP, Scripting or WMI)Example ZenPacks.zenoss.SolarisMonitorModeler Plugins - PythonSee Model Extensions above for what modeler plugins are. Python modeler plugins only differ in that you extend <strong>the</strong>PythonPlugin class, and must implement <strong>the</strong> collection logic in addition to <strong>the</strong> processing logic.The collect method implementation may return data normally, or it may return a Twisted deferred to takeadvantage of <strong>the</strong> asynchronous modeling engine. It is recommended to use <strong>the</strong> deferred approach whenever possibleto avoid blocking <strong>the</strong> zenmodeler daemon while <strong>the</strong> collect method executes.Complexity 7Skills <strong>Zenoss</strong>, Python, TwistedExample ZenPacks.zenoss.OpenStack1.3. ZenPack Taxonomy 83


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseModel ExtensionsWhen <strong>the</strong> standard model of <strong>the</strong> <strong>Zenoss</strong> platform doesn’t cover an object or property you need in your ZenPack, <strong>the</strong>model can be extended. Existing model classes such as Device, FileSystem or IpInterface can be extended, and entirelynew types of components can be created.The typical requirements for extended <strong>the</strong> model include at least <strong>the</strong> following steps.1. Create a Python class2. Create an API interface and adapter3. Wire up <strong>the</strong> API with ZCML4. Write JavaScript to tailor <strong>the</strong> display of your component5. Write a modeler pluginComplexity 8Skills <strong>Zenoss</strong>, ZCML, Python, JavaScriptExample ZenPacks.zenoss.OpenStackDaemonsA new daemon must be written only if none of <strong>the</strong> existing daemons can perform <strong>the</strong> task required by your ZenPack.The zencommand daemon is <strong>the</strong> usual last resort for custom collection requirements if none of <strong>the</strong> more specializeddaemons will work. See Command DataSource Plugins and Command DataSource Parsers for what can be done byzencommand.There is a common collector framework that should be used to perform much of <strong>the</strong> typical daemon functionality suchas configuration and scheduling in a consistent way. To use this you should create a CollectorDaemon object,configure it with a class that implements <strong>the</strong> ICollectorPreferences interface and create a task class thatimplements <strong>the</strong> IScheduledTask interface.In almost all cases you will also need to create a ZenHub service to build <strong>the</strong> configuration for your new daemon. Thisservice should subclass HubService or one of its existing more specialized subclasses.Complexity 9Skills <strong>Zenoss</strong>, Python, TwistedExample ZenPacks.zenoss.ZenVMwarePlatform ExtensionPlatform extensions are any implementations added to a ZenPack that doesn’t fall into any of <strong>the</strong> previously-definedcomplexity elements. Due to <strong>the</strong> flexibility of ZenPacks, <strong>the</strong>se could be almost anything.The DistributedCollector example given below falls into this category because it extends <strong>the</strong> simple flat collectorstructure in <strong>the</strong> core <strong>Zenoss</strong> platform to be a tiered hub and collector structure. It also adds extensive hub and collectormanagement capabilities.Complexity 10Skills <strong>Zenoss</strong>, ZCML, Python, JavaScript, etc.Example ZenPacks.zenoss.DistributedCollector1.3. ZenPack Taxonomy 84


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, Release1.3.2 Example ZenPack ClassificationsZenPacks.zenoss.ApacheMonitorClassificationFunctionalityMaintainerAvailabilityQA LevelComplexityValueMonitoring<strong>Zenoss</strong> EngineeringOpen SourceQ.A. TestedConfigurationCommand DataSource PluginsDataSource TypesZenPacks.zenoss.IISMonitorClassificationFunctionalityMaintainerAvailabilityQA LevelComplexityValueMonitoring<strong>Zenoss</strong> EngineeringAvailable with <strong>Zenoss</strong> SubscriptionQ.A. TestedConfigurationZenPacks.zenoss.DistributedCollectorClassificationFunctionalityMaintainerAvailabilityQA LevelComplexityValuePlatform Extension<strong>Zenoss</strong> EngineeringAvailable with <strong>Zenoss</strong> SubscriptionQ.A. TestedConfigurationUser InterfacePlatform Extension1.3. ZenPack Taxonomy 85


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseZenPacks.zenoss.RANCIDIntegratorClassificationFunctionalityMaintainerAvailabilityQA LevelComplexityValueIntegration<strong>Zenoss</strong> EngineeringAvailable with <strong>Zenoss</strong> SubscriptionQ.A. TestedConfigurationEvent Class Transforms and MappingsScriptsZenPacks.zenoss.DatabaseMonitorClassificationFunctionalityMaintainerAvailabilityQA LevelComplexityValueMonitoring<strong>Zenoss</strong> EngineeringAvailable with <strong>Zenoss</strong> SubscriptionQ.A. TestedConfigurationCommand DataSource PluginsDataSource TypesZenPacks.zenoss.ZenVMwareClassificationFunctionalityMaintainerAvailabilityQA LevelComplexityValueMonitoring<strong>Zenoss</strong> EngineeringAvailable with <strong>Zenoss</strong> SubscriptionQ.A. TestedConfigurationEvent Class Transforms and MappingsDataSource TypesUser InterfaceImpact AdaptersETL AdaptersModel ExtensionsDaemons1.3. ZenPack Taxonomy 86


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseZenPacks.zenoss.SolarisMonitorClassificationFunctionalityMaintainerAvailabilityQA LevelComplexityValueMonitoring<strong>Zenoss</strong> EngineeringAvailable with <strong>Zenoss</strong> SubscriptionQ.A. TestedConfigurationCommand DataSource PluginsCommand DataSource ParsersModeler Plugins - SNMP, COMMAND, WMIZenPacks.zenoss.ImpactClassificationFunctionalityMaintainerAvailabilityQA LevelComplexityValuePlatform Extension<strong>Zenoss</strong> EngineeringAdditional Cost with <strong>Zenoss</strong> SubscriptionQ.A. TestedConfigurationUser InterfaceImpact AdaptersDaemonsPlatform ExtensionZenPacks.zenoss.OpenStackClassificationFunctionalityMaintainerAvailabilityQA LevelComplexityValueMonitoring<strong>Zenoss</strong> EngineeringOpen SourceUntestedConfigurationEvent Class Transforms and MappingsCommand DataSource PluginsCommand DataSource ParsersUser InterfaceImpact AdaptersModeler Plugins - PythonModel Extensions1.3. ZenPack Taxonomy 87


<strong>Zenoss</strong> <strong>Labs</strong> <strong>Documentation</strong>, ReleaseZenPacks.zenoss.ServiceNowIntegratorClassificationFunctionalityMaintainerAvailabilityQA LevelComplexityValueIntegration<strong>Zenoss</strong> ServicesAvailable with <strong>Zenoss</strong> SubscriptionQ.A. TestedConfigurationUser InterfaceModel ExtensionsDaemonsZenPacks.community.ZenODBCClassificationFunctionalityMaintainerAvailabilityQA LevelComplexityValuePlatform Extension<strong>Zenoss</strong> CommunityOpen SourceQ.A. TestedDataSource TypesModeler Plugins - Python1.3. ZenPack Taxonomy 88


CHAPTER 2<strong>Documentation</strong> FormatsThis documentation is available in <strong>the</strong> following formats.• HTML• PDF• Epub• Manpage89


CHAPTER 3ContributionThis documentation is generated automatically every time a change is made to <strong>the</strong> zenosslabs repository. The mostdirect route for contribution would be to fork that repository, make <strong>the</strong> desired change to <strong>the</strong> documentation and senda pull request.See GitHub’s Fork a Repo documentation if you’re unfamiliar with this process. O<strong>the</strong>rwise, email <strong>the</strong> address belowto have someone incorporate your changes for you.90


CHAPTER 4ContactQuestions and comments related to this documentation should sent to labs@zenoss.com.91

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!