4.2. Python application variables¶
If the default set of monitoring variables for your devices is insufficient, you can modify it and configure the system to poll additional SNMP OIDs. You can use the same facility to change OIDs the system polls by default by chaning or modifying them. To do this, you need to build a small Python application.
This application should be stored in the script called var_builder.py in directory
scripts in your Git repository. The application defines a class based on class
VariableBuilder
. First, lets look at the base class and its structure:
Class VariableBuilder
has just one method execute() that takes one parameter, a device object.
It relies on few specialized “builder” classes that
generate variables for various aspects of the device, its hardware components, and interfaces.
All these classes are based on the same base class and must have method make_variables():
An instance of each “builder” class is created in the method __init__() of the runner class and then registered with the system. It looks like this:
class VariableBuilder(object):
"""
Monitoring variable descriptors are created by the actual builder classes
`DeviceVariableBuilder`, `HWComponentVariableBuilder`, `ProtocolDescriptorVariableBuilder`
and others.
:param log: Java logger object. Call it using self.log.info("log message")
"""
def __init__(self, log):
self.log = log
self.device = DeviceVariableBuilder(log)
self.interface = InterfaceVariableBuilder(log)
self.hw_components = HWComponentVariableBuilder(log)
self.protocols = ProtocolDescriptorVariableBuilder(log)
self.chassis_alarms = ChassisAlarmVariableBuilder(log)
self.mpls_tunnels = MplsTunnelVariableBuilder(log)
self.fw_counters = FwCounterVariableBuilder(log)
self.lb = LoadBalancerVariableBuilder(log)
self.vpn = VpnDeviceVariableBuilder(log)
self.ipsec_tunnels = IpsecTunnelVariableBuilder(log)
self.azure_resources = AzureResourceVariableBuilder(log)
Note names of the fields used to hold references to various builder classes. If you want to change the way monitoring variables are created, you need to build your own class derived from one of the builder classes and register it in your own derived runner. Here is an example where we override classes that build variables for network interfaces and load balancers:
from variable_builders import InterfaceVariableBuilder
from variable_builders import LoadBalancerVariableBuilder
from variable_builders.variable_builder_runner import VariableBuilder
class ModifiedInterfaceVarBuilder(InterfaceVariableBuilder):
"""
This class overrides checks implemented in the base class `InterfaceVariableBuilder` to make
NetSpyGlass monitor interfaces it would not monitor by default, or skip some that it
monitors
"""
def custom_interface_match(self, device, intf):
# implement custom logic here, for example check
# device and interface name and tags to decide if it should be monitored.
# or return True to monitor all interfaces of all devices (this can be too much though)
return True
class ModifiedLoadBalancerVariableBuilder(LoadBalancerVariableBuilder):
"""
This class is used to build monitoring variables for load balancer devices
"""
def __init__(self, log):
super(LoadBalancerVariableBuilder, self).__init__(log)
def make_global_variables(self, device):
# implement custom logic here
return super(ModifiedLoadBalancerVariableBuilder, self).make_global_variables()
def make_vserver_variables(self, device, vserver):
# implement custom logic here
return super(ModifiedLoadBalancerVariableBuilder, self).make_vserver_variables(device, vserver)
def make_server_pool_variables(self, device, pool):
# implement custom logic here
return super(ModifiedLoadBalancerVariableBuilder, self).make_server_pool_variables(device, pool)
def make_lb_node_variables(self, device, lb_node):
# implement custom logic here
return super(ModifiedLoadBalancerVariableBuilder, self).make_lb_node_variables(device, lb_node)
class ModifiedVariableBuilder(VariableBuilder):
"""
This class is based on the standard VariableBuilder but substitutes
classes used to build variables for network interfaces and load balancers
:param log: Java logger object. You can call it using self.log.info("log message")
"""
def __init__(self, log):
super(ModifiedVariableBuilder, self).__init__(log)
# Override these builder objects with our new classes
self.interface = ModifiedInterfaceVarBuilder(log)
self.lb = ModifiedLoadBalancerVariableBuilder(log)
See section How to override the defaults below for more detailed examples.
4.2.1. Selection of components to monitor¶
Python scripts used to generate monitoring variables also decide which h/w components or interfaces should be monitored. Functions have access to attributes name, address, tags and others both for the device and its components and interfaces.
For example, function InterfaceVariableBuilder.make_variables() analyzes tags of interfaces to decide which should be monitored. Class InterfaceVariableBuilder defines few functions that do this. Here is an example of a function that checks if the interface is in admin state ‘Up’ and is not a loopback or some kind of internal:
def is_interesting_interface(self, intf):
"""
This function implements some basic checks to decide if we want to monitor this interface
:type intf: PyNetworkInterface
:param intf: PyNetworkInterface wrapper object
:return: True if we want to monitor this interface
"""
return ('ifAdminStatus.Up' in intf.tags and
not 'ifRole.LoopbackInterface' in intf.tags and
not 'ifRole.OutOfBandManagement' in intf.tags and
not 'ifRole.SimulatedInterface' in intf.tags and
not 'ifRole.Internal' in intf.tags)
Tags are added to the interface object by the discovery process and tag selection Python hook script (see document tags.md)
4.2.2. The structure of the variable definition dictionary¶
In the end, functions in the builder classes return a dictionary where the key is variable name and value is a dictionary that describes the variable.
This dictionary has the following structure:
{
'sysUpTime': {
'snmp_oid': 'SNMPv2-MIB:sysUpTime.0'
'index': 2,
'name': 'SNMP Agent',
'type': MonitoringDataType.TimeTick,
},
'snmpInTotalReqVars': {
'snmp_oid': 'SNMPv2-MIB:snmpInTotalReqVars.0',
'index': 2,
'name': 'SNMP Agent',
'type': MonitoringDataType.Counter,
}
}
In this example we create monitoring variables with names “sysUpTime” and “snmpInTotalReqVars”, each variable is described by its own dictionary that list its parameters. Most of the items in this dictionary are optional except for snmp_oid. Also, either component or index must be present.
The dictionary can have the following keys:
component
The value of this dictionary item is a reference to the hardware component or interface for which this variable is created. Both h/w component and interface objects have the name and index, so if the key component is provided, name and index become optional. However if you provide name or index in addition to component, they will override values read from the corresponding h/w component or interface.
name
The name of the component. If you provide item with the key component, the name will be taken from the corresponding object. However if you provide both, the value of the item with the key name overrides.
index
The index of the component or ifIndex of an interface. For example variable that monitors CPU load of the device has index equal to the index of the CPU we discover on the device (some devices may have multiple processors or cores). Variables that monitor values related to network interfaces have index equal to the ifIndex of the interface (defined in RFC1213 MIB).
Normally you don’t have to specify index explicitly but instead, you just provide reference to the component using key component. However you need to set index explicitly for variables that do not correspond to any h/w component or interface we discover, for example “snmp timeouts” for the device or “the size of the routing table”. The requirement for the index is that it must be unique for the given variable and device. Different variables can have coinciding indexes, for example variables ‘ifHCInOctets’ and ‘ifHCOutOctets’ refer to the same interface by its ifIndex and therefore have the same value of the index attribute. However different instances of the same variable ifHCInOctets must have different index values. If you create monitoring variables for something that does not obtain its index from the discovery process, you’ll have to generate your own. Here is how this looks like (this fragment is taken from the script python/variable_builders/device.py, function DeviceVariableBuilder.make_variables() with minor modifications):
class DeviceVariableBuilder(BaseVariableBuilder):
def make_variables(self, device):
"""
Given device object, build set of monitoring variables
for it
:type device: PyDevice
:param device: network device object
:return: a dictionary where the key is variable name and value is another dictionary
"""
assert isinstance(device, PyDevice)
return {
'snmpTimeoutsPercentage': {
'snmp_oid': Constants.snmpTimeoutsOid,
'index': 1,
'name': 'SNMP Agent',
'type': MonitoringDataType.Gauge,
},
'snmpInTotalReqVars': {
'snmp_oid': 'SNMPv2-MIB:snmpInTotalReqVars.0',
'index': 2,
'name': 'SNMP Agent',
'type': MonitoringDataType.Counter,
},
'sysUpTime': {
'snmp_oid': 'SNMPv2-MIB:sysUpTime.0',
'index': 2,
'name': 'SNMP Agent',
'type': MonitoringDataType.TimeTick,
},
}
This function creates three monitoring variables: snmpTimeoutsPercentage, snmpInTotalReqVars and sysUpTime that do not correspond to any particular h/w component or network interface we can discover, so it needs to assign index explicitly. The actual value of the index does not matter as long as it satisfies the requirement listed above.
snmp_oid
This item defines SNMP OID that will be used to poll the device. You can either use explicit dot-separated numeric value or use “MIB-NAME:OID-NAME.subindex” format illustrated in the example above. Note that since the function has access to the device and h/w component or interface object, you can use index or other parameters discovered by NetSpyGlass. For example, the following function uses ifIndex of the interface (see script python/variable_builders/interface.py, function InterfaceVariableBuilder.make_basic_vars()):
class InterfaceVariableBuilder(BaseVariableBuilder):
def make_basic_vars(self, intf):
"""
Generate configuration for the basic set of monitoring variables
for the interface: interface utilization, errors, discards
:type intf: PyNetworkInterface
:param intf: PyNetworkInterface wrapper object
:return: a dictionary where the key is variable name and value is another dictionary
"""
if_index = intf.if_index
in_octets_oid = 'IF-MIB:ifInOctets.{0}'.format(if_index)
out_octets_oid = 'IF-MIB:ifOutOctets.{0}'.format(if_index)
if intf.if_high_speed > 0:
in_octets_oid = 'IF-MIB:ifHCInOctets.{0}'.format(if_index)
out_octets_oid = 'IF-MIB:ifHCOutOctets.{0}'.format(if_index)
return {
# ---------------------------------------------------------------
'ifHCInOctets': {
'component': intf,
'snmp_oid': in_octets_oid
},
'ifHCOutOctets': {
'component': intf,
'snmp_oid': out_octets_oid
},
This function takes ifIndex of the interface from the attribute if_index of PyNetworkInterface object passed as an argument and checks if interface supports 64 bit counters by looking at the attribute if_high_speed. If the value of if_high_speed is greater than zero, it assumes 64-bit part of the IF-MIB is supported. Here you can also see how snmp_oid can be constructed using the MIB and OID names and the value of if_index.
tags
This item allows you to inject tags into generated monitoring variable at this stage. Note that the system will automatically add tags assigned to the device and component to the variable, however this item allows you to add tags that do not already belong to either device or component. Here is how this item can be used to add forwarding class name as a tag in facet CoS to the CoS metrics variable:
def make_cos_vars(self, device, intf):
"""
Generate configuration for the set of COS monitoring variables
for the interface: tail and red drops.
:type device: PyDevice
:param device: network device object
:type intf: PyNetworkInterface
:param intf: PyNetworkInterface wrapper object
:return: a dictionary where the key is variable name and value is another dictionary
"""
if_index = intf.if_index
cos_vars = {}
if 'Vendor.Juniper' in device.tags:
configured_queues = intf.cos_queues
for queue in configured_queues:
fc_number = device.getFcNumber(queue)
fc_name_tag = 'CoS.{0}'.format(device.getFcName(fc_number))
cos_vars['jnxCosTailDropPktsQueue{0}'.format(queue)] = {
'component': intf,
'snmp_oid': 'JUNIPER-COS-MIB:jnxCosQstatTailDropPkts.{0}.{1}'.format(if_index, queue),
'tags': fc_name_tag
}
return cos_vars
a call to function getFcName() of the PyDevice
gets forwarding class name for
given queue number.
Note that the value of this item can be either a list of strings or just a single string.
type
This item is used to define data type if it can not be deduced from the SNMP OID definition in the MIB. NetSpyGlass needs to know the type to properly compute rates and convert some of the values, such as OIDs that return time in “time ticks” (1/100 of a second). Data type is set to the value of Java enum MonitoringDataType as follows:
from net.happygears.nw2.py import MonitoringDataType
class DeviceVariableBuilder(BaseVariableBuilder):
def make_variables(self, device):
return {
'sysUpTime': {
'snmp_oid': 'SNMPv2-MIB:sysUpTime.0',
'index': 2,
'name': 'SNMP Agent',
'type': MonitoringDataType.TimeTick,
},