ska_snmp_device.SNMPDevice

This is a generic SNMP Tango device class for monitoring and control of any device that supports SNMP. It’s configured using a YAML definition file, which maps SNMP objects to Tango attributes, which are dynamically created when the device is initialised. Here’s a basic example defining a single attribute:

attributes:
  - name: deviceDescription
    oid: [ SNMPv2-MIB, sysDescr, 0 ]

Attributes are defined with reference to SNMP MIBs - files that describe which SNMP objects are available on a particular device. The oid field is made up of a MIB name (“SNMPv2-MIB” in the example above), an object name (“sysDescr”) and one or more indexes. For more info about these, see below.

The most important Tango properties supported by SNMPDevice are:

  • Model, the model name of the device

  • Host, the IP or DNS name of the SNMP endpoint of the device under control

  • Port, the port of the SNMP endpoint, which defaults to 161

  • Community, the SNMPv2 community name to use

  • Traps, defines whether traps are enabled

  • TrapHost, generally the same as Host (maybe different for testing)

  • TrapPort, defaults to 162 but maybe different for testing

Getting started with local development

  1. Clone this repo.

  2. Obtain a MIB file for the device you want to control. You can find MIBs on vendor websites, on mibs.pysnmp.com, on http://www.circitor.fr or elsewhere on the internet. (Enlogic PDUs have a link to download their MIB in their web admin interface - perhaps others too?)

  3. Write a device definition file (see below).

  4. Put your MIB in mib_library. This step can be skipped if the MIB is hosted on mibs.pysnmp.com, in which case PySMI’s MIB compiler should automatically download it.

  5. Determine the host, port and SNMP community of your SNMP device.

  6. Create your Tango device however you create Tango devices. Set the attributes Host, Port, Community and Model appropriately

The configuration file syntax

The device_definition file for any device must contain 4 mandatory fields which will be validated against a schema:

  • name, the attribute name

  • oid, the object identifier

  • polling_period, how often to update the attribute

  • access, read or write capability

The following example defines a minimal device for an Enlogic EN6808 PDU, with attributes for reporting and setting the state of each of the 24 outlets:

attributes:
  - name: deviceDescription
    oid: [ SNMPv2-MIB, sysDescr, 0 ]
    polling_period: .inf
    access: read

  - name: firmwareVersion
    oid: [ ENLOGIC-PDU-MIB, pduNamePlateFirmwareVersion, 1 ]
    polling_period: .inf
    access: read

  - name: serialNumber
    oid: [ ENLOGIC-PDU-MIB, pduNamePlateSerialNumber, 1 ]
    polling_period: .inf
    access: read

  - name: outlet{}State
    oid: [ENLOGIC-PDU-MIB, pduOutletSwitchedSTATUSState, 1]
    polling_period: 10000
    access: read
    indexes:
      - [1, 24]

  - name: outlet{}Command
    oid: [ENLOGIC-PDU-MIB, pduOutletSwitchedControlCommand, 1]
    indexes:
      - [1, 24]
    polling_period: 10000
    access: read

name

Pretty straightforward. This defines the Tango attribute’s name. But note the {} s in outlet{}Command and outlet{}State - these are place holders which are used when indexes is defined.

oid

Defines the SNMP object this attribute will represent, in the form:

<mib-name>, <object name>, [<index>, ...].

The MIB name must correspond to a MIB file present on the filesystem, in $PWD/mib_library, /usr/share/snmp/mibs, or /usr/share/mibs (these last two are defaults from PySNMP, the SNMP library we use under the hood). Any MIBs that the selected MIB imports from must ALSO exist in one of those directories.

The object name should be one that’s defined within the specified MIB file.

The indexes provided as part of the oid field will depend on the object. Scalar objects - those that aren’t part of an SNMP table - should always be specified with the single index 0. Tabular objects’ indexes will depend on the table definition.

If the indexes key is defined, the OID will be extended as described below.

indexes

SNMP objects can either be scalar - that is, there is only one of them - or part of a table, in which case multiple indexed instances of the object exist. With indexes we can define attributes for tabular objects without having to repeat ourselves for every instance.

When indexes is present, an attribute is created for each item in the range specified by indexes - in this case, 1 to 24. The name field is templated (as a Python format string) with the index value. In the example above, the device would end up with attributes outlet1State, outlet2State, etc. The index value will be appended to the oid for each attribute.

For example, the outlet{}State definition above is equivalent to:

- name: outlet1State
  oid: [ENLOGIC-PDU-MIB, pduOutletSwitchedSTATUSState, 1, 1]
- name: outlet2State
  oid: [ENLOGIC-PDU-MIB, pduOutletSwitchedSTATUSState, 1, 2]

outlets 3 to 23:

- name: outlet24State
  oid: [ENLOGIC-PDU-MIB, pduOutletSwitchedSTATUSState, 1, 24]

polling_period

It’s handy to be able to query different SNMP objects at different rates. For example, the value of the serialNumber attribute above will never change. polling_period sets the minimum time between polls for each attribute. The default is to query the SNMP object on every iteration of the internal polling loop, which by default runs every two seconds.

Setting polling_period: 10000 means the object won’t be queried any more frequently than once every 10 seconds. Setting it to polling_period: .inf means it will be polled only once.

If the polling period is set to zero the attribute will not be polled and it will be considered to be a trap and updated asynchronously.

access

Client access to attributes can be specified with the keyword access with values that determine what level of access is allowed. The following values are allowed:

  • read

  • readonly

  • read-only

  • write

  • writeonly

  • write-only

  • readwrite

  • read-write

For example:

- name: outlet24State
  oid: [ENLOGIC-PDU-MIB, pduOutletSwitchedSTATUSState, 1, 24]
  access: readonly

optional fields

The following list declares some of the more frequent optional configuration parameters used to describe the devices attributes

  • min_value the minimum attribute value

  • max_value the maximum attribute value

  • min_warning the minimum attribute value before a warning is triggered

  • max_warning the maximum attribute value before a warning is triggered

  • min_alarm the minimum attribute value before an alarm is triggered

  • max_alarm the maximum attribute value before an alarm is triggered

  • description a description of the attribute

  • dtype attribute type one of int, float, boolean, str, enum

  • enum_labels if dtype is enum then specify the labels list eq. [“1”, “2”, “3”]

Roadmap

  • Use BULK operations

  • Ability to generate Tango commands, not only attributes

More about MIBs and OIDs

SNMP objects are organised in a global hierarchy. Each object is given an OID which is a globally-unique sequence of numbers - for example, the OID for the sysDescr object in the definition above is 1.3.6.1.2.1.1.1. Each object defined in a MIB also has a textual representation, which we use in our definition file.

Read more about how MIBs and OIDs work here.

Providing multiple indexes

You’ll note that in the examples, indexes is a list of lists. Usually you will only need to provide one index for an attribute, but it’s possible to provide more. For example, the pduOutletSwitchedSTATUSState object we refer to above actually has two indexes - the first indicates the PDU ID, the second indicates the outlet ID. However, since we aren’t dealing with daisy-chained PDUs, we just baked the fixed PDU ID 1 into the oid field in our example.

When multiple indexes are provided, one attribute is created for each element of the cartesian product of each of the indexes. For example, if you have indexes [1, 2] and [5, 6], attributes will be created for the index values (1, 5), (1, 6), (2, 5) and (2, 6). These values will be appended to the attributes’ OIDs, and passed positionally to Python’s str.format() when generating the attributes’ names.

Our outlet state attribute above could have been defined more verbosely as:

- name: outlet{1}State
  oid: [ENLOGIC-PDU-MIB, pduOutletSwitchedSTATUSState]  # no indexes here!
  indexes:
    - [1, 1]
    - [1, 24]

Note we use {1} instead of {} in the name template - this means we’re referring to the second (zero-indexed) index