Device modeling
This 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 the attribute to the
model, modeling it from the device, and displaying it in the overview
screen for NetBotz devices.
Starting in this section we’ll be working with files within the NetBotz
ZenPack’s directory. To keep the path names short, I’ll assume
the $ZP_TOP_DIR and $ZP_DIR environment variables have been set as follows.
export ZP_TOP_DIR=/z/ZenPacks.training.NetBotz
export ZP_DIR=$ZP_TOP_DIR/ZenPacks/training/NetBotz
Create the NetBotzDevice class
A Device subclass should not be confused with a device class. In the
previous section we created the /NetBotz device class from the web
interface. Creating a Device subclass means to extend the actual
Python class of a Device object. You’d do this to add new attributes,
methods or relationships to special device types.
Use the following steps to create a NetBotzDevice class with a new
attribute called temp_sensor_count.
-
Update
$ZP_DIR/zenpack.yamlto contain following contents.zenpack.yamlname: ZenPacks.training.NetBotz classes: NetBotzDevice: base: [zenpacklib.Device] label: NetBotz properties: temp_sensor_count: type: int device_classes: /NetBotz: zProperties: zPythonClass: ZenPacks.training.NetBotz.NetBotzDevice zSnmpMonitorIgnore: false zCollectorPlugins: - training.snmp.NetBotz - zenoss.snmp.NewDeviceMap - zenoss.snmp.DeviceMap - zenoss.snmp.InterfaceMap-
The
namefield is mandatory and must match the full Python module name of your ZenPack. -
The classes section is where we define extensions to the standard Zenoss model. In this case we’re creating a special device type called
NetBotzDevicebecause we want to add a new property calledtemp_sensor_count. For more information about defining classes, see Classes and relationships. -
The
device_classessection allows us to also configure the/NetBotzdevice class in YAML. Note that we’re configuring the same options that we already set through the web interface. You can set them either way, but once you add a device class tozenpack.yamlyou’ll likely find its easier to maintain all of the information in one place.The most important property we’re setting on the
/NetBotzdevice class iszPythonClass. This is required so that the newNetBotzDeviceclass we’ve defined will be used for devices in this device class.You’ll also note that we’re adding
training.snmp.NetBotzto the list of modeler plugins (zCollectorPlugins) even though it doesn’t yet exist. This is safe to do, and we’ll shortly be creating the modeler plugin.
-
-
Reinstall the ZenPack to have the device class changes made.
zenpack --link --install $ZP_TOP_DIR -
Restart
Zopeprocess so the web interface can load our new module.serviced service restart zope -
Reset the Python class of our existing device.
Run
zendmdand execute the following snippet.device = find("Netbotz01") print device.__class__You should see
<class 'Products.ZenModel.Device.Device'>. We see this instead of the Python class we just created because thezPythonClassproperty is only used when a new device is created in a device class, or when a device is moved into a device class with a differingzPythonClassvalue.So we have two options for getting our NetBotz device to use the new Python class we created. We can either delete the device and add it back, or move it to a different device class and back. Actually, there’s a third option that I use most frequently to solve this problem. I move it into the same device class using
zendmd. Execute the following snippet withinzendmdto reset the device’s Python class.dmd.Devices.NetBotz.moveDevices('/NetBotz', 'Netbotz01') commit() device = find("Netbotz01") print device.__class__Now you should see
<class 'ZenPacks.training.NetBotz.NetBotzDevice.NetBotzDevice'>printed. This confirms that ourDevicesubclass works, and that we’ve configurezPythonClasscorrectly for the/NetBotzdevice class.
Find temperature sensor count
Before we can write a modeler plugin to populate our
new temp_sensor_count attribute, we need to figure out how to get the
information. There are a few ways we could approach this. One way is to
use that NETBOTZV2-MIBas a reference to see if we can find anything
about temperature sensors specifically.
Find temperature information in NETBOTZV2-MIB using the following
command.
smidump -f identifiers /usr/share/snmp/mibs/NETBOTZV2-MIB.mib | grep -Ei temp
You should see the following in the output:
NETBOTZV2-MIB tempSensorTable table 1.3.6.1.4.1.5528.100.4.1.1
NETBOTZV2-MIB tempSensorEntry row 1.3.6.1.4.1.5528.100.4.1.1.1
NETBOTZV2-MIB tempSensorId column 1.3.6.1.4.1.5528.100.4.1.1.1.1
NETBOTZV2-MIB tempSensorValue column 1.3.6.1.4.1.5528.100.4.1.1.1.2
NETBOTZV2-MIB tempSensorErrorStatus column 1.3.6.1.4.1.5528.100.4.1.1.1.3
NETBOTZV2-MIB tempSensorLabel column 1.3.6.1.4.1.5528.100.4.1.1.1.4
NETBOTZV2-MIB tempSensorEncId column 1.3.6.1.4.1.5528.100.4.1.1.1.5
NETBOTZV2-MIB tempSensorPortId column 1.3.6.1.4.1.5528.100.4.1.1.1.6
NETBOTZV2-MIB tempSensorValueStr column 1.3.6.1.4.1.5528.100.4.1.1.1.7
NETBOTZV2-MIB tempSensorValueInt column 1.3.6.1.4.1.5528.100.4.1.1.1.8
NETBOTZV2-MIB tempSensorValueIntF column 1.3.6.1.4.1.5528.100.4.1.1.1.9
You’ll also see another node and a bunch of notification entries.
These are related to SNMP traps, and not relevant to what we’re
interested in polling right now.
What we see here is that there isn’t a single OID we can request that
will tell us the number of temperature sensors. We’re going to have to
do an snmpwalk of the table then count how many rows are in the
response. Specifically we want to remember the name and OID for
the row: tempSensorEntry. Due to the hierarchical nature of a MIBs
representation this is the most specific OID that will return the data
we need.
snmpwalk 1172.17.0.1 1.3.6.1.4.1.5528.100.4.1.1.1
You’ll see a lot of output that starts with:
NETBOTZV2-MIB::tempSensorId.21604919 = STRING: nbHawkEnc_1_TEMP
NETBOTZV2-MIB::tempSensorId.1095346743 = STRING: nbHawkEnc_0_TEMP
NETBOTZV2-MIB::tempSensorId.1382714817 = STRING: nbHawkEnc_2_TEMP1
NETBOTZV2-MIB::tempSensorId.1382714818 = STRING: nbHawkEnc_2_TEMP2
NETBOTZV2-MIB::tempSensorId.1382714819 = STRING: nbHawkEnc_2_TEMP3
NETBOTZV2-MIB::tempSensorId.1382714820 = STRING: nbHawkEnc_2_TEMP4
NETBOTZV2-MIB::tempSensorId.1382714833 = STRING: nbHawkEnc_3_TEMP1
NETBOTZV2-MIB::tempSensorId.1382714834 = STRING: nbHawkEnc_3_TEMP2
NETBOTZV2-MIB::tempSensorId.1382714865 = STRING: nbHawkEnc_1_TEMP1
NETBOTZV2-MIB::tempSensorId.1382714866 = STRING: nbHawkEnc_1_TEMP2
NETBOTZV2-MIB::tempSensorId.1382714867 = STRING: nbHawkEnc_1_TEMP3
NETBOTZV2-MIB::tempSensorId.1382714868 = STRING: nbHawkEnc_1_TEMP4
NETBOTZV2-MIB::tempSensorId.2169088567 = STRING: nbHawkEnc_3_TEMP
NETBOTZV2-MIB::tempSensorId.3242830391 = STRING: nbHawkEnc_2_TEMP
What you’re seeing above is the tempSensorId column for all 14 rows in
the tempSensorTable. Continuing on you will see 14 rows for each of the
other columns in the table.
Create a modeler plugin
The next step is to build a modeler plugin. A modeler plugin’s
responsibility reach out into the world, gather data, and plug it into
the attributes and relationships of our model classes. In this example,
this means to make the SNMP requests necessary to determine how many
temperature sensors a NetBotz device has, and populate
our temp_sensor_count attribute with the result.
Use the following steps to create our modeler plugin.
-
Make the directory that’ll contain our modeler plugin.
mkdir -p $ZP_DIR/modeler/plugins/training/snmpNote that we’re using our ZenPack’s
trainingnamespace, thensnmp. This is the recommended approach to make it clear what protocol the modeler plugin will use, and to avoid our modeler plugin conflicting with one from someone else’s ZenPack. -
Create
__init__.pyordunder-initfiles.touch $ZP_DIR/modeler/__init__.py touch $ZP_DIR/modeler/plugins/__init__.py touch $ZP_DIR/modeler/plugins/training/__init__.py touch $ZP_DIR/modeler/plugins/training/snmp/__init__.pyThese empty
__init__.pyfiles are mandatory if we ever expect Python to import modules from these directories. -
Create
$ZP_DIR/modeler/plugins/training/snmp/NetBotz.pywith the following contents.from Products.DataCollector.plugins.CollectorPlugin import ( SnmpPlugin, GetTableMap, ) class NetBotz(SnmpPlugin): snmpGetTableMaps = ( 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()), })-
Start by importing
SnmpPluginandGetTableMapfrom Zenoss.SnmpPluginwill handle all of the SNMP requests for us and present the results in a format we can easily work with.GetTableMapwill be used here because we need to request an SNMP table rather than specific OIDs. -
Our
NetBotzclass extendsSnmpPlugin. Note that theNetBotzclass name must match the filename (module name) of the modeler plugin. -
By defining
snmpGetTableMapsas a tuple or list on our class we can add aGetTableMapobject that requests that 1.3.6.1.4.1.5528.100.4.1.1.1 row OID and specify that we only want to get the first (.1) column and name ittempSensorId. -
The
processmethod will receive a two-element tuple containing the SNMP request results in therequestparameter. The first elememt,results\[0\], of this tuple would be any direct OID gets of which we didn’t request any in this plugin. The second element,results\[1\]will contain a dictionary of the table results. In this caseresults\[1\]would look like the 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', }, } -
We then extract just the
tempSensorTableresults intotemp_sensorsto make the nextreturnline a bit easier to understand. -
We then return a dictionary that sets the
temp_sensor_countkey’s value to the number of keys intemp_sensors. Actually we return a dictionary that’s been wrapped in an ObjectMap by the modeler plugin’sobjectMaputility method.The
processmethod within all modeler plugins must return one of the following types of data.- None (makes no changes to the model).
- ObjectMap (to apply directly to the device that’s being modeled).
- RelationshipMap (to apply to a relationship within the device).
- A list containing zero or more ObjectMap and/or RelationShipMap objects.
An
ObjectMapis simply adictwrapped with some meta-data. ARelationshipMapis alistwrapped with some meta-data and containing zero or moreObjectMapinstances.
-
-
Restart
Zopeandzenhubto load the new module.serviced service restart zope serviced service restart zenhub
Test the modeler plugin
Now that we’ve created and enabled a basic modeler plugin, we should test it.
-
Remodel the NetBotz device.
You can do this from the web interface, but I usually use the command line because it can be easier to work with if further debugging is necessary.
zenmodeler run --device=Netbotz01 -
Execute the following snippet in
zendmd.device = find("Netbotz01") print device.temp_sensor_countYou should see
14printed as the number of temperature sensors.
Change the device overview
The next step will be to show the number of temperature sensors to users of the web interface. We’ll replace the Memory/Swap field in the top-left box of the device overview page with the count of temperature sensors.
Follow these steps to customize the device Overview page.
-
Create a directory to store our ZenPack’s JavaScript.
mkdir -p $ZP_DIR/resources -
Create
$ZP_DIR/resources/device.jswith the following contents.Ext.onReady(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') }); }); });- Wait for
Extto be ready. - Find the overview summary panel (top-left on Overview page)
- Remove the
memoryfield. - Add our
temp_sensor_countfield.
Collection Zones and Resource Manager use ExtJS. You can find more about manipulating objects in this way in their documentation.
- Wait for
Test the device overview
That’s it. We can restart Zope and navigate to our NetBotz device’s
overview page in the browser interface. You should
see # Temperature Sensors label with a value of 14 at the bottom of
the top-left panel.