Graphing the output from a python script (or adding it as a custom application)

Hi!

I began using LibreNMS to monitor the network I administer. Now we’re testing the alerting capabilities, wich are awesome.

One of the devices that are being monitored is a Cisco ASA, and I’m very pleased with the information that LibreNMS can grab from it.

Now the problem: We’re doing some traffic shaping on the ASA, and I need to graph the packets that are being dropped. As there isn’t a SNMP OID for this, We created a script that access the device via Telnet/SSH and scraps the data from the show command.

The python script is this:

from napalm import get_network_driver

def open_connection(host,username,password,device_type):
   driver = get_network_driver(device_type)
   device = driver(host,username,password)
   device.open()
   return device

def show_policy_map(connection):
   command = ['sh service-policy police | begin WebFilter_rate_limit']
   output = connection.cli(command)
   #output = connection.send_command('sh service-policy police | begin WebFilter_rate_limit')
   #print (output)
   return output

asa = open_connection('SOME_HOST','SOME_USER','SOME_PASS','asa')
text = show_policy_map(asa)
lista = list(text.values())
lista2 = lista[0].splitlines()
input_policy=lista2[5].split()
output_policy=lista2[10].split()

# Printout the data needed
print(input_policy)
print(output_policy)

Is it possible to do this?

Thanks in advance for any advice, tips & help! :exploding_head:

It looks like you’re pretty close to having built your own Nagios style plugin which you could use as a service in LibreNMS. You would just need to get the output right, see: https://www.monitoring-plugins.org/doc/guidelines.html#PLUGOUTPUT

Then enable services if you haven’t already, put your script in the nagios plugin directory and set them up as services in LNMS.

Here’s sort of a pseudo code example of what that might look like based on the your input rate piece of your script…

#!/usr/bin/env python

import argparse
import sys

from napalm import get_network_driver


def open_connection(host,username,password,device_type):
   driver = get_network_driver(device_type)
   device = driver(host,username,password)
   device.open()
   return device


def show_policy_map(connection):
   command = ['sh service-policy police | begin WebFilter_rate_limit']
   output = connection.cli(command)
   return output


def main(args):
  asa = open_connection(args.H, args.u, args.p, 'asa')
  text = show_policy_map(asa)
  lista = list(text.values())
  lista2 = lista[0].splitlines()
  input_policy = lista2[5].split()

  status = [0, 'OK']
  if int(input_policy) >= args.w: status = [1, 'WARNING']
  if int(input_policy) >= args.c: status = [2, 'CRITICAL']
  
  # Printout input data
  print('{} - {} webfilter input rate|rate={};{};{};;'.format(
      status[1],
      input_policy,
      input_policy,
      str(args.w),
      str(args.c)
  ))

  return status[0]


if __name__ == '__main__':

    # Setup CLI arugments
    parser = argparse.ArgumentParser(prog='check_webfilter-input.py')
    parser.add_argument('-H', help='hostname', type=str)
    parser.add_argument('-w', help='warning threshold', type=int)
    parser.add_argument('-c', help='critical threshold ', type=int)
    parser.add_argument('-u', help='username', type=str)
    parser.add_argument('-p', help='password', type=str)
    args = parser.parse_args()

    sys.exit(main(args))
2 Likes

Thanks for your support @slashdoom!

That script guided me to create the one I need.

#!/usr/bin/env python

import argparse
import sys

from napalm import get_network_driver


def open_connection(host,username,password,device_type):
   driver = get_network_driver(device_type)
   device = driver(host,username,password,optional_args = {'secret': password})
   device.open()
   return device


def show_policy_map(connection,police):
   command = ['sh service-policy police | begin {}'.format(police)]
   output = connection.cli(command)
   return output


def main(args):
  asa = open_connection(args.H, args.u, args.p, 'asa')
  text = show_policy_map(asa, args.l)
  lista = list(text.values())
  lista2 = lista[0].splitlines()
  input_exceeded_pkts =  lista2[4].split()                   
  output_exceeded_pkts = lista2[9].split()
  input_exceeded_bps =  lista2[5].split()
  #print input_exceeded_bps
  output_exceeded_bps =  lista2[10].split()
  #print output_exceeded_bps

  
  cir = lista2[2].split()
  #print cir
  w_cir = int(cir[1]) * args.w / 100
  #print w_cir
  c_cir = int(cir[1]) * args.c / 100
  #print c_cir

  status = [0, 'OK']
  if int(input_exceeded_bps[4]) or int(output_exceeded_bps[4]) >= w_cir: status = [1, 'WARNING']
  if int(input_exceeded_bps[4]) or int(output_exceeded_bps[4]) >= c_cir: status = [2, 'CRITICAL']

  # Printout input data
  # This is the expected format:
  # STATUS: 'label'=value[UOM];[warn];[crit];[min];[max]
  print("{}: {}|'IN_pkts'={} 'OUT_pkts'={}".format(
      status[1],
      str(args.l),
      input_exceeded_pkts[1],
      output_exceeded_pkts[1],
  ))

  return status[0]


if __name__ == '__main__':

    # Setup CLI arugments
    parser = argparse.ArgumentParser(prog='check_webfilter-input.py')
    parser.add_argument('-H', help='hostname', type=str)
    parser.add_argument('-w', help='warning threshold (%cir)', type=int)
    parser.add_argument('-c', help='critical threshold (%cir)', type=int)
    parser.add_argument('-u', help='username', type=str)
    parser.add_argument('-p', help='password', type=str)
    parser.add_argument('-l', help='police name', type=str)
    args = parser.parse_args()

    sys.exit(main(args))

A service debug gives:

/opt/librenms$ sudo ./check-services.php -d
DEBUG!
Starting service polling run:

SQL[SELECT D.*,S.*,attrib_value  FROM `devices` AS D INNER JOIN `services` AS S ON S.device_id = D.device_id AND D.disabled = 0  LEFT JOIN `devices_attribs` as A ON D.device_id = A.device_id AND A.attrib_type = "override_icmp_disable" ORDER by D.device_id DESC; [] 1.34ms] 
  

Nagios Service - 4  
Request:  '/opt/librenms/nagios/plugins/check_asa-service-policy' '-H' 'hua-pil-fw1' '-w' '10' '-c' '20' '-u' 'user' '-p' 'pass' '-l' 'WebFilter_rate_limit'  
Perf Data - DS: IN_pkts, Value: 3374799, UOM:  
Perf Data - DS: OUT_pkts, Value: 107931, UOM:  
Response: OK: WebFilter_rate_limit  
Service DS: {
    "IN_pkts": "",
    "OUT_pkts": ""
}  
RRD[last hua-pil-fw1/services-4.rrd  --daemon unix:/var/run/rrdcached.sock]
RRD[create hua-pil-fw1/services-4.rrd --step 300 DS:IN_pkts:GAUGE:600:0:U DS:OUT_pkts:GAUGE:600:0:U  RRA:AVERAGE:0.5:1:2016 RRA:AVERAGE:0.5:6:1440 RRA:AVERAGE:0.5:24:1440 RRA:AVERAGE:0.5:288:1440  RRA:MIN:0.5:1:720 RRA:MIN:0.5:6:1440     RRA:MIN:0.5:24:775     RRA:MIN:0.5:288:797  RRA:MAX:0.5:1:720 RRA:MAX:0.5:6:1440     RRA:MAX:0.5:24:775     RRA:MAX:0.5:288:797  RRA:LAST:0.5:1:1440  -O --daemon unix:/var/run/rrdcached.sock]
RRD[update hua-pil-fw1/services-4.rrd N:3374799:107931 --daemon unix:/var/run/rrdcached.sock]
./check-services.php 2019-05-31 01:38:29 - 1 services polled in 0.855 secs  

Now awaiting perf data to be plotted, but until now it show NaN

Maybe to add a check :
if value = “” then value = “0”
So it plot 0 ?

Leaved it all night and its working. The graph shows the packet drop by the policy.

Is there a way to show in/out in just only one graph?

1 Like

Nice. I don’t think one graph is currently possible though. I think it’s sort of RRD/services limitation. RRD needs to know the number of series when the file is created but since that could change with services it just does a file per series.

Ok.

Here’s the last version of my script, in case someone need it.

You can define the default user/pass if you don’t want to pass it via an argument (and leave it visible to all admins in LibreNMS).

#!/usr/bin/env python

import argparse
import sys

from napalm import get_network_driver

# DEFAULT USER
default_user = 'someuser'
default_pass = 'somepass'


def open_connection(host,username,password,device_type):
   driver = get_network_driver(device_type)
   device = driver(host,username,password,optional_args = {'secret': password})
   device.open()
   return device


def show_policy_map(connection,police):
   command = ['sh service-policy police | begin {}'.format(police)]
   output = connection.cli(command)
   return output


def main(args):

  username = default_user if not args.u else args.u
  if args.debug >= 2: print ('Username: {}').format(username)
  password = default_pass if not args.p else args.p
  if args.debug >= 2: print ('Password: {}').format(password)

  # Grab RAW data from device
  asa = open_connection(args.H, username, password, 'asa')
  
  # Scrap information
  text = show_policy_map(asa, args.l)
  lista = list(text.values())
  lista2 = lista[0].splitlines()
  input_exceeded_pkts =  lista2[4].split()
  if args.debug >= 1: print ('IN exceeded pkts:  {}').format(input_exceeded_pkts[1])
  output_exceeded_pkts = lista2[9].split()
  if args.debug >= 1: print ('OUT exceeded pkts: {}').format(output_exceeded_pkts[1])
  input_exceeded_bps =  lista2[5].split()
  if args.debug >= 1: print ('IN exceeded bps:  {}').format(input_exceeded_bps[4])
  output_exceeded_bps =  lista2[10].split()
  if args.debug >= 1: print ('OUT exceeded bps: {}').format(output_exceeded_bps[4])

  cir = lista2[2].split()
  if args.debug >= 1: print ('CIR: {}').format(cir[1])
  w_cir = int(cir[1]) * args.w / 100
  if args.debug >= 1: print ('%CIR Warning:  {}').format(w_cir)
  c_cir = int(cir[1]) * args.c / 100
  if args.debug >= 1: print ('%CIR Critical: {}').format(c_cir)

  status = [0, 'OK']
  if int(input_exceeded_bps[4]) > w_cir or int(output_exceeded_bps[4]) > w_cir: status = [1, 'WARNING']
  if int(input_exceeded_bps[4]) > c_cir or int(output_exceeded_bps[4]) > c_cir: status = [2, 'CRITICAL']
  if args.debug >= 1: print ('STATUS: {}').format(status)


  # Printout input data
  # This is the expected format:
  # STATUS: 'label'=value[UOM];[warn];[crit];[min];[max]
  print("{}: {} {} Mbps|'IN_pkts'={}c 'OUT_pkts'={}c".format(
      status[1],
      str(args.l),
      str(int(cir[1])/1000000),
      input_exceeded_pkts[1],
      output_exceeded_pkts[1],
  ))

  return status[0]


if __name__ == '__main__':

    # Setup CLI arugments
    parser = argparse.ArgumentParser(prog='check_asa-service-policy')
    parser.add_argument('-H', help='hostname', type=str)
    parser.add_argument('-w', help='warning threshold (%cir)', type=int)
    parser.add_argument('-c', help='critical threshold (%cir)', type=int)
    parser.add_argument('-u', help='username', type=str)
    parser.add_argument('-p', help='password', type=str)
    parser.add_argument('-l', help='police name', type=str)
    parser.add_argument('-debug', help='debug data [1|2]', type=int)
    args = parser.parse_args()

    sys.exit(main(args))
1 Like