Datasource plugin (events)
Now that we have one or more locations modeled on
our api.weather.gov device, we’ll want to start monitoring each
location. Using PythonCollector we have the ability to create events,
record datapoints and even update the model. We’ll start with an example
that creates events from weather alert data.
The idea will be that we’ll create events for locations that have outstanding weather 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.
Using PythonCollector
Before using a Python plugin in our ZenPack, we must make sure we install the PythonCollector ZenPack, and make it a requirement for our ZenPack.
The PythonCollector ZenPack adds the capability to write high
performance datasources in Python. They will be collected by
the zenpython service that comes with the PythonCollector ZenPack.
I’d recommend reading the
PythonCollector documentation
for more information. Note that commercial versions of Zenoss include
PythonCollector.
Installing PythonCollector
The first thing we’ll need to do is to make sure the PythonCollector ZenPack is installed on our system. If it isn’t, follow these instructions to install it.
-
Download the latest release from the PythonCollector page.
-
Run the following command to install the ZenPack:
zenpack --install ZenPacks.zenoss.PythonCollector-<version>.egg -
Restart Zenoss.
Add PythonCollector dependency
Since we’re going to be using PythonCollector capabilities in our ZenPack we must now update our ZenPack to define the dependency.
Follow these instructions to define the dependency.
- Navigate to Advanced > Settings > ZenPacks.
- Click into the ZenPacks.training.NWS ZenPack.
- Check ZenPacks.zenoss.PythonCollector in the list of dependencies.
- Click Save.
- Export the ZenPack.
Create the alerts plugin
Follow these steps to create the Alerts data source plugin:
-
Create
$ZP_DIR/dsplugins.pywith the following contents."""Monitors current conditions using the NWS API.""" # Logging import logging LOG = logging.getLogger('zen.NWS') # stdlib Imports import json import time from datetime import datetime from dateutil import parser # Twisted Imports from twisted.internet.defer import inlineCallbacks, returnValue from twisted.web.client import getPage # PythonCollector Imports from ZenPacks.zenoss.PythonCollector.datasources.PythonDataSource import ( PythonDataSourcePlugin, ) class Alerts(PythonDataSourcePlugin): """NWS alerts data source plugin.""" @classmethod def config_key(cls, datasource, context): return ( context.device().id, datasource.getCycleTime(context), context.id, 'nws-alerts', ) @classmethod def params(cls, datasource, context): return { 'county': context.county, 'station_name': context.title, } @inlineCallbacks def collect(self, config): data = self.new_data() for datasource in config.datasources: try: response = yield getPage( 'https://api.weather.gov/alerts/active?zone={query}' .format( query=datasource.params['county'])) response = json.loads(response) except Exception: LOG.exception( "%s: failed to get alerts data for %s", config.id, datasource.params['station_name']) continue for rawAlert in response.get('features'): alert = rawAlert['properties'] severity = None expires = parser.parse(alert['expires']) if datetime.timetuple(expires) <= time.gmtime(): severity = 0 elif alert['certainty'] == 'Likely': severity = 4 else: severity = 3 data['events'].append({ 'device': config.id, 'component': datasource.component, 'severity': severity, 'eventKey': 'nws-alert-{}'.format(alert['event'].replace(' ', '')), 'eventClassKey': 'nws-alert', 'summary': alert['headline'], 'message': alert['description'], 'nws-sender': alert['senderName'], 'nws-date': alert['effective'], 'nws-expires': alert['expires'], 'nws-category': alert['category'], 'nws-instruction': alert['instruction'], 'nws-type': alert['event'], }) returnValue(data)Let’s walk through this code to explain what is being done.
-
Logging
The first thing we do is import
loggingand createLOGas our logger. It’s important that the name of the logger in thelogging.getLogger()begins withzen.. You will not see your logs otherwise.The stdlib and Twisted imports are almost identical to what we used in the modeler plugin, and they’re used for the same purposes.
Finally we import
PythonDataSourcePluginfrom the PythonCollector ZenPack. This is the class our data source plugin will extend, and basically allows us to write code that will be executed by thezenpythoncollector service. -
AlertsclassUnlike our modeler plugin, there’s no need to make the plugin class’ name the same as the filename. As we’ll see later when we’re setting up the monitoring template that will use this plugin, there’s no specific name for the file or the class required because we configure where to find the plugin in the datasource configuration within the monitoring template.
-
config_keyClass MethodThe
config_keymethod must have the@classmethoddecorator. It is passeddatsource, andcontext. Thedatasourceargument will be the actual datasource that the user configures in the monitoring templates section of the web interface. It has properties such aseventClass,severity, and as you can see agetCycleTime()method that returns the interval at which it should be polled. Thecontextargument will be the object to which the monitoring template and datasource is bound. In our case this will be a station object such as Dallas Fort Worth International Airport.The purpose of the
config_keymethod is to split monitoring configuration into tasks that will be executed by thezenpythonservice. Thezenpythonservice will create one task for each unique value returned fromconfig_key. It should be used to optimize the way data is collected. In some cases it is possible to make a single query to an API to get back data for many components. In these cases it would be wise to removecontext.idfrom the config_key so we get one task for all components.In our case, the NWS API must be queried once per location so it makes more sense to put
context.idinconfig_keyso we get one task per station.The value returned by
config_keywill be used whenzenpythonlogs. So adding something likenws-alertsto the end makes it easy to see logs related to collecting alerts in the log file.The
config_keymethod will only be executed byzenhub. So you must restartzenhubif you make changes to theconfig_keymethod. This also means that if there’s an exception in theconfig_keymethod it will appear in thezenhublog, notzenpython. -
paramsclass methodThe
paramsmethod must have the@classmethoddecorator. It is passed the samedatasourceandcontextarguments asconfig_key.The purpose of the
paramsmethod is to copy information from the Zenoss database into theconfig.datasources[*]that will be passed as an argument to thecollectmethod. Since thecollectmethod is run byzenpythonit won’t have direct access to the database, so it relies on theparamsmethod to provide it with any information it will need to collect.In our case you can see that we’re copying the context’s
county, andstation_idproperties. These will be used in thecollectmethod.Just like the
config_keymethod,paramswill only be executed byzenhub. So be sure to restartzenhubif you make changes, and look in thezenhublog for errors. -
collectmethodThe
collectmethod does all of the real work. It will be called once per cycletime. It gets passed aconfigargument which for the most part has two useful properties:config.idandconfig.datasources.config.idwill be the device’s id, andconfig.datasourcesis a list of the datasources that need to be collected.You’ll see in the
collectmethod that each datasource inconfig.datasourceshas some useful properties.datasource.componentwill be the id of the component against which the datasource is run, or blank in the case of a device-level monitoring template.datasource.paramscontains whatever theparamsmethod returned.Within the body of the collect method we see that we create a new
datavariable usingdata = self.new_data().datais a place where we stick all of the collected events, values and maps.datalooks like the following:data = { 'events': [], 'values': defaultdict(<type 'dict'>, {}), 'maps': [], }Next we iterate over every configured datasource. For each one we make a call to the NWS’s Alerts API, then iterate over each alert in the response creating an event for each.
The following standard fields are being set for every event. You should read Zenoss’ event management documentation if the purpose of any of these fields is not clear. I highly recommend setting all of these fields to an appropriate value for any event you send into Zenoss to improve the ability of Zenoss and Zenoss’ operators to manage the events.
device: Mandatory. The device id related to the event.component: Optional. The component id related to the event.severity: Mandatory. The severity for the event.eventKey: Optional. A further uniqueness key for the event. Used for de-duplication and clearing.eventClassKey: Optional. An identifier for thetypeof event. Used during event class mapping.summary: Mandatory: A (hopefully) short summary of the event. Truncated to 128 characters.message: Optional: A longer text description of the event. Not truncated.
You will also see many
nws-*fields being added to the event. Zenoss allows arbitrary fields on events so it can be a good practice to add any further information you get about the event in this way. It can make understanding and troubleshooting the resulting event easier.Finally we return data with all of events we appended to it.
zenpythonwill take care of getting the events sent from this point.
-
-
Restart Zenoss.
After adding a new datasource plugin you must restart Zenoss. While developing it’s enough to just restart
zenhubwith the following command.serviced service restart zenhubThat’s it. The datasource plugin has been created. Now we just need to do some Zenoss configuration to allow us to use it.
Configure monitoring templates
Rather than use the web interface to manually create a monitoring
template, we’ll specify it in our zenpack.yaml instead.
-
Edit
$ZP_DIR/zenpack.yamland add thetemplatessection below to the existing/NWSdevice class.device_classes: /NWS: templates: Station: description: Weather monitoring using the NWS API. targetPythonClass: ZenPacks.training.NWS.NwsStation datasources: alerts: type: Python plugin_classname: ZenPacks.training.NWS.dsplugins.Alerts cycletime: "600"At least some of this should be self-explanatory. The YAML vocabulary has been designed to be as intuitive and concise as possible. Let’s walk through it.
-
The highest-level element (based on indentation) is
/NWS/templates/Station. This means to create aStationmonitoring template in the/NWSdevice class.The monitoring template must be called
Stationbecause that is thelabelfor theNwsStationclass to which we want the template bound. -
The
descriptionis for documentation purposes and should describe the purpose of the monitoring template. -
The
targetPythonClassis a hint to what type of object the template is meant to be bound to. Currently this is only used to determine if users should be allowed to manually bind the template to device classes or devices. Providing a valid component type like we’ve done prevents users from making this mistake. -
Next we have
datasourceswith a singlealertsdatasource defined.The
alertsdatasource only has three properties:type: This is what makeszenpythoncollect the dataplugin_classname: This is the fully-qualified class name for thePythonDataSourceplugin we created that will be responsible for collecting the datasource.cycletime: The interval in seconds at which this datasource should be collected.
-
-
Reinstall the ZenPack to add the monitoring templates.
Some sections of
zenpack.yamlsuch as zProperties and templates only get created when the ZenPack is installed.Run the usual command to reinstall the ZenPack in development mode.
zenpack --link --install $ZP_TOP_DIR -
Navigate to Advanced > Monitoring Templates in the web interface to verify that the Station monitoring template has been created as defined.
Test monitoring weather alerts
-
Go to the following URL for the current severe weather map of the United States: https://www.weather.gov
-
Click on one of the colored areas. Orange and red are more exciting. This will take you to the text of the warning. It should reference city or county names.
-
Update
zNwsStateson theapi.weather.govdevice to add one of the states that has an active weather alert. For example, “ND”, for North Dakota. -
Remodel the
api.weather.govdevice then verify that the new location is modeled. -
Run the following command to collect from
api.weather.gov.zenpython run -v10 --device=api.weather.govThere will be a lot of output from this command, but we’re mainly looking for an event to be sent for the weather alert. It will look similar to the following output:
2019-05-29 16:19:04,832 DEBUG zen.zenpython: Queued event (total of 93) {'nws-type': u'Flash Flood Watch', 'nws-expires': u'2019-05-29T17:00:00-05:00', 'severity': 3, 'nws-category': u'Met', 'eventClassKey': 'nws-alert', 'component': 'KBVO', 'monitor': 'localhost', 'agent': 'zenpython', 'summary': u'Flash Flood Watch issued May 29 at 4:28AM CDT until May 30 at 7:00AM CDT by NWS Tulsa OK', 'nws-sender': u'NWS Tulsa OK', 'manager': '6328c71e5335', 'nws-date': u'2019-05-29T04:28:00-05:00', 'rcvtime': 1559146744.832561, 'device_guid': '021a3692-2a94-412d-a221-aa579ec2fecf', 'device': 'api.weather.gov', 'message': u'The Flash Flood Watch continues for\n\n* Portions of Arkansas and Oklahoma...including the following\ncounties...in Arkansas...Benton...Carroll...Crawford...\nFranklin...Madison...Sebastian and Washington AR. in\nOklahoma...Adair...Cherokee...Choctaw...Craig...Creek...\nDelaware...Haskell...Latimer...Le Flore...Mayes...McIntosh...\nMuskogee...Nowata...Okfuskee...Okmulgee...Osage...Ottawa...\nPawnee...Pittsburg...Pushmataha...Rogers...Sequoyah...Tulsa...\nWagoner and Washington OK.\n\n* Through Thursday morning\n\n* Widespread showers and thunderstorms will expand across the\nregion on today and continue into tonight. The heaviest rainfall\nis expected to be southeast of Interstate 44 with the heaviest\ntotals from southeast Oklahoma into northwest Arkansas.\nWidespread 1 to 2 inch rain amounts with local amounts over 3\ninches appear likely within the heaviest rainfall band. The\nArkansas River remaining in historic flood category will inhibit\nsmall creeks and tributaries from draining as they normally\nwould. Despite the lack of preceding rainfall, rapid onset flash\nflooding will be possible given this condition. Cities through\nwest central Arkansas near the Arkansas river may experience\nflooding in areas that typically remain dry and there is\npotential for catastrophic flooding should the heavier totals be\nrealized near the larger Arkansas river channel. Flooding should\nbe considered a high risk weather impact today through tonight.', 'nws-instruction': u'If you are in the watch area, keep informed, and be ready for\nquick action if flash flooding is observed or if a warning is\nissued.\n\nA flash flood watch means rapidly rising water or flooding is\npossible within the watch area.', 'eventKey': 'nws-alert-FlashFloodWatch'}You can use a commmand like the following to see just these log lines:
zenpython run -v10 --device=api.weather.gov | grep 'Queued' | grep 'nws'You should now be able to confirm that this event was created in the Zenoss event console.