Component Modeling¶
This section will cover creation of a custom Component subclass, creation of a relationship to our NetBotDevice class, and modeling of the components to fill the relationship.
In the 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 the temperature being reported by each of these sensors. So that’s what we’ll do. Modeling each sensor as a component allows Zenoss to automatically discover and monitor sensors regardless of how many a particular device has.
Find Temperature Sensor Attributes¶
In the 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 use smidump and snmpwalk for a refresher on what’s available.
Find temperature information in NETBOTZV2-MIB using the following command.
smidump -f identifiers /usr/share/snmp/mibs/NETBOTZV2-MIB.mib | egrep -i 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
Let’s now use snmpwalk to see what these values look like on our NetBotz device.
snmpwalk 127.0.1.113 1.3.6.1.4.1.5528.100.4.1.1.1
You should see a lot of output that begins with the following:
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
Note the 21604919 in the first response. This is the SNMP index of the first temperature sensor, or the first row in the table. I like to then restrict my snmpwalk results to only show this row with a command like the following.
snmpwalk 127.0.1.113 1.3.6.1.4.1.5528.100.4.1.1.1 | grep "\.21604919 ="
Which will show us the value of each column for that one temperature sensor:
NETBOTZV2-MIB::tempSensorId.21604919 = STRING: nbHawkEnc_1_TEMP
NETBOTZV2-MIB::tempSensorValue.21604919 = INTEGER: 265
NETBOTZV2-MIB::tempSensorErrorStatus.21604919 = INTEGER: normal(0)
NETBOTZV2-MIB::tempSensorLabel.21604919 = STRING: Temperature
NETBOTZV2-MIB::tempSensorEncId.21604919 = STRING: nbHawkEnc_1
NETBOTZV2-MIB::tempSensorPortId.21604919 = STRING:
NETBOTZV2-MIB::tempSensorValueStr.21604919 = STRING: 26.500000
NETBOTZV2-MIB::tempSensorValueInt.21604919 = INTEGER: 26
NETBOTZV2-MIB::tempSensorValueIntF.21604919 = INTEGER: 79
Now we have everything we should need to make decisions about what attributes we should model for our sensors and which would better be collected as datasources to have thresholds applied and plotted over time on graphs.
My initial thoughts would be to model the following as attributes.
- tempSensorId
- tempSensorEncId (enclosure ID)
- tempSensorPortId
I would then want to collect tempSensorValueStr as a datasource because it offers the best precision. Zenoss is capable of handling numeric strings so we don’t have to collect tempSensorValue and divide it by 10 like other systems might.
Create a Component Subclass¶
Use the following steps to create a NetBotzTemperatureSensor class with the attributes discovered above.
Update
$ZP_DIR/zenpack.yaml
to include the following NetBotzTemperatureSensor entry in the classes section, and the new class_relationships section.classes: NetBotzDevice: base: [zenpacklib.Device] label: NetBotz properties: temp_sensor_count: type: int NetBotzTemperatureSensor: base: [zenpacklib.Component] label: Temperature Sensor properties: enclosure: label: Enclosure port: label: Port class_relationships: - NetBotzDevice 1:MC NetBotzTemperatureSensor
It’s important to pick class names that will be unique. The best practice is to use a short prefix based on the ZenPack’s name followed by the type of thing the class represents as is being done here.
Both of the new properties should be strings. Since string is the default type, we don’t need to specify it. This just leaves the label.
Note
Despite noting above that we always wanted to model the tempSensorId attribute, we aren’t adding an attribute for it here. This is because DeviceComponent already has both an id and title attribute that wherein we can store the value of tempSensorId.
The class_relationships section is very important. We could never have any temperature sensors in the system if we didn’t relate them to something else. The 1:MC between the two class names describes the type of relationship. Specifically it says that one NetBotzDevice can contain many NetBotzTemperatureSensor objects. See Relationships for more information.
Test TemperatureSensor Class¶
With our component class defined and relationships setup we can use zendmd to make sure we didn’t make any mistakes. Execute the following snippet in zendmd.
from ZenPacks.training.NetBotz.NetBotzTemperatureSensor import NetBotzTemperatureSensor
sensor = NetBotzTemperatureSensor('test_sensor_01')
device = find("Netbotz01")
device.netBotzTemperatureSensors._setObject(sensor.id, sensor)
sensor = device.netBotzTemperatureSensors._getOb(sensor.id)
print sensor
print sensor.device()
You’ll most likely get the following error when executing the above snippet:
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: netBotzTemperatureSensors
This error is indicating that we have no netBotzTemperatureSensors relationship on the device object. This would seemingly make no sense because we just added it. The key here is that existing objects like the Netbotz01 device don’t automatically get new relationships. We have to either delete the device and add it again, or execute the following in zendmd to create the newly- defined relationship.
device.buildRelations()
commit()
Now you can go back and run the original snippet again. You should see the name of the sensor and device objects printed if everything worked as planned.
Update the Modeler Plugin¶
As with the NetBotzDevice class, the 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 the temperature sensor components, but we’ll update the NetBotz modeler plugin we previously created to model the sensors instead.
Edit
$ZP_DIR/modeler/plugins/training/snmp/NetBotz.py
and replace its contents with the following.from Products.DataCollector.plugins.CollectorPlugin import ( SnmpPlugin, GetTableMap, ) class NetBotz(SnmpPlugin): relname = 'netBotzTemperatureSensors' modname = 'ZenPacks.training.NetBotz.NetBotzTemperatureSensor' 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', {}) rm = self.relMap() for snmpindex, row in temp_sensors.items(): name = row.get('tempSensorId') if not name: log.warn('Skipping temperature sensor with no name') continue rm.append(self.objectMap({ 'id': self.prepId(name), 'title': name, 'snmpindex': snmpindex.strip('.'), 'enclosure': row.get('tempSensorEncId'), 'port': row.get('tempSensorPortId'), })) return rm
Let’s take a closer look at how we changed the modeler plugin.
We added relname and modname as class attributes.
These two settings control the meta-data that will automatically be set when the self.relMap and self.objectMap methods are called in the process method.
Setting relname to
netBotzTemperatureSensors
will cause the self.relMap call to create a RelationshipMap that will be applied to the netBotzTemperatureSensors relationship defined on the NetBotzDevice object.Setting modname to
ZenPacks.training.NetBotz.TemperatureSensor
will cause the self.objectMap calls in the process method to create ObjectMap instances that will be turned into instances of our TemperatureSensor class.We’re now requesting the tempSensorEncId and tempSensorPortId columns be returned in the SNMP table request results. We’ll use these to populate their corresponding fields on the TemperatureSensor class.
Most of the process method has been changed.
We’re now creating a RelationshipMap and appending an ObjectMap to it for each temperature sensor in the results. We use the self.relMap and self.objectMap utility methods to make this easier.
Restart zopectl and zenhub to load the changed module.
Test the Modeler Plugin¶
We already added the training.snmp.NetBotz modeler plugin the the /NetBotz device class in an earlier exercise. So we only need to run zenmodeler to test the temperature sensor modeling updates.
Run
zenmodeler run --device=Netbotz01
We should see Changes in configuration applied near the end of zenmodeler’s output. The changes referred to should be 14 temperature sensor objects being created and added to the device’s netBotzTemperatureSensors relationship.
Check the Netbotz01 device in the web interface. The temperature sensors should now be visible.