NeDi and Observium or LibreNMS

NeDi and Observium are two of my favourites network monitoring tools.

I do like to deploy both, they complete each other and since they're free there is no need to choose. The only problem with the use of two tools is the integration.

Observium has a limitation with host discovery: a device can be discovered only by name and that name must be resolved to an IP via DNS or hosts file. Since most of my customers don't register network devices on DNS that means manually populate /etc/hosts file. On the other hand NeDi doesn't have this limitation and has a strong network/discovery mechanism.

Can we make them to work together? Of course, that's open source!

Installation

NeDi and Observium installation processes are well explained here and here, just follow the instructions, they work fine.

Customization

I've modified

/var/nedi/nedi.conf

to include only the address ranges of my managed devices:

netfilter      ^10\.5\.|^10\.16\.0\.|^192\.168\.20\.

and

/var/nedi/seedlist

whith a list of devices/communities that will be used as a seed for the discovery process in the format:

ip  community1,community2

For the sake of simplicity I just run two virtual hosts on Apache2, NeDi runs on port 80, Observium runs on port 81, edit

/etc/apache2/sites-enabled/000-default 

as follows:

<VirtualHost *:80>
        ServerAdmin webmaster@localhost

        DocumentRoot /var/nedi/html
        <Directory />
                Options FollowSymLinks
                AllowOverride None
        </Directory>
        <Directory /var/www/>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride None
                Order allow,deny
                allow from all
        </Directory>

        ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
        <Directory "/usr/lib/cgi-bin">
                AllowOverride None
                Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
                Order allow,deny
                Allow from all
        </Directory>

        ErrorLog ${APACHE_LOG_DIR}/error.log
            
        LogLevel warn

        CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

<VirtualHost *:81>
  DocumentRoot /opt/observium/html/
  ServerName  observium.domain.com
  CustomLog /opt/observium/logs/access_log combined
  ErrorLog /opt/observium/logs/error_log
  <Directory "/opt/observium/html/">
    AllowOverride All
    Options FollowSymLinks MultiViews
  </Directory>
</VirtualHost>

Integration

The integration between the two tools is meant to take advantage of the discovery performed by NeDi to populate /etc/hosts file and then feed the discovery process of Observium.

Step by step

Where is the data?

NeDi stores all the information on a MySQL database. A quick look on the tables and we can see how to extract the devices information:

/usr/bin/mysql -uroot -pPASSWORD -B -e "select devip,device from devices" nedi 

Replace PASSWORD with the password you typed during the software installation.

Tweak this extraction a little bit to clean the list:

  • filter lines that don't begin with a number in range [1-9], that means no valid IP address was discovered via CDP/LDP
  • remove some chars (slash,spaces) that Observium doesn't like in hostnames with sed commands
  • uniform all the device names in uppercase chars

If your device hostnames have other chars that don't work well with Observium just add more sed to the script.

This is the final result:

/usr/bin/mysql -uroot -pPASSWORD -B -e "select devip,device,readcomm from devices" nedi | grep "^[1-9]" |sed -e 's/ /_/g' | sed -e 's/\//_/g' | sort | tr '[:lower:]' '[:upper:]'

IP format

NeDi stores IP addresses in undotted decimal format, we need to convert them to dotted decimal to use them.

We can use Python for that, let's create /root/undotteddecimaltoip.py:

#!/usr/bin/env python

import sys
inFile = sys.argv[1]
#outFile = sys.argv[2]

def int_to_dqn(st):
    """
    Convert integer to dotted quad notation
    """
    st = "%08x" % (st)
    return "%i.%i.%i.%i" % (int(st[0:2],16),int(st[2:4],16),int(st[4:6],16),int(st[6:8],16)

f = open(inFile,"r")
for line in f:
        values = line.split("\t")
        print int_to_dqn(int(values[0])+" "+values[1].rstrip().replace(" ","_")
f.close()

Test it running:

/root/undotteddecimaltoip.py /tmp/nedidevicelist.txt

You can see now all the IP addresses are now in dotted decimal format.  

Populate /etc/hosts file

Now we can put together the Python script we just created and a few bash commands to update the hosts file:

if [ -f "/etc/hosts.original" ]
then
        cat /etc/hosts.original > /etc/hosts
else
        cp /etc/hosts /etc/hosts.original
fi

/root/undotteddecimaltoip.py /tmp/nedidevicelist.txt >> /etc/hosts

Notice that the original hosts files is maintained, if we need to enter lines that shouldn't be lost just edit /etc/hosts.original file.  

Add devices to Observium

We can finally use the data from NeDi to add devices to Observium:

while IFS=$'\t' read ip name comm
do
echo -e "NOW ADDING DEVICE $name"
/opt/observium/add_device.php $name $comm
done < /tmp/nedidevicelist.txt

Notice that $comm is the actual community NeDi used to discover the devices, even if in seedlist more than one was specified.

Put it all together

This is the final script:

#!/bin/bash
/usr/bin/mysql -uroot -pPASSWORD -B -e "select devip,device,readcomm from devices" nedi | grep "^[1-9]" |sed -e 's/ /_/g' | sed -e 's/\//_/g' | sort | tr '[:lower:]' '[:upper:]' > /tmp/nedilist.txt

if [ -f "/etc/hosts.original" ]
then
        cat /etc/hosts.original > /etc/hosts
else
        cp /etc/hosts /etc/hosts.original
fi

/root/undotteddecimaltoip.py /tmp/nedidevicelist.txt >> /etc/hosts

while IFS=$'\t' read ip name comm
do
echo -e "NOW ADDING DEVICE $name"
/opt/observium/add_device.php $name $comm
done < /tmp/nedidevicelist.txt

/opt/observium/discovery.php -h new >> /dev/null 2>&1
/opt/observium//poller.php -h all  >> /dev/null 2>&1

Now just remember to add to cron the correct files to perform auto discovery with NeDi and then feed Observium again to keep them updated.

Verify

How can we verify it worked? One simple way could be to extract the device list from NeDi and from Observium and see if they match:

/usr/bin/mysql -uroot -pPASSWORD -B -e "select device from devices" nedi | sed -e 's/ /_/g' | sed -e 's/\//_/g' | sort | tr '[:lower:]' '[:upper:]' > /tmp/nedilist.txt
/usr/bin/mysql -uroot -pPASSWORD -B -e "select hostname from devices" observium | sed -e 's/ /_/g' | sed -e 's/\//_/g' | sort | grep -v "^AP" | tr '[:lower:]' '[:upper:]' > /tmp/observiumlist.txt
diff /tmp/observiumlist.txt /tmp/nedilist.txt

If the list in Observium is shorter check if any hostname contains a symbol that can't be read by Observium.

See the result

Open a browser to port 80, login and go to Devices-->List-->Show to se all the devices discovered by NEDI.

Open a browser to port 81 to see the Observium interface and check all the devices.

Nice!

Wrap up

Open source software has many advantages, in this particular case it allowed me to integrate two different software in one solution. Fell free to suggest improvements to the script in the comments, I consider this just a POC.

WARNING

I'm not a systems engineer or a programmer, use my code at your own risk, I take no responsibility for any damage caused by any of the script shown.

UPDATE 20160109 - from Observium to LibreNMS

Observium has been forked to LibreNMS due to different views about GPL licensing.

Installation of both is quite easy, just follow the instructions on each website.

I updated my scripts to work with LibreNMS:

#!/bin/bash

\//_/g' | sort | grep -v "^AP" | tr '[:lower:]' '[:upper:]' > /tmp/nedilist.txt

PASSWORD=$1

/usr/bin/mysql -uroot -p$PASSWORD -B -e "select devip,device,readcomm from devices" nedi | grep "^[1-9]" |sed -e
 's/ /_/g' | sed -e 's/\//_/g' | sort | tr '[:lower:]' '[:upper:]' > /tmp/nedidevicelist.txt

if [ -f "/etc/hosts.original" ]
then
        cat /etc/hosts.original > /etc/hosts
else
        cp /etc/hosts /etc/hosts.original
fi

/root/undotteddecimaltoip.py /tmp/nedidevicelist.txt >> /etc/hosts

while IFS=$'\t' read ip name comm
do
echo -e "NOW ADDING DEVICE $name"
/opt/librenms/addhost.php $name ${comm,,} v2c
done < /tmp/nedidevicelist.txt

# DISCOVER AN POLL
/opt/librenms/discovery.php -h new >> /dev/null 2>&1
/opt/librenms/poller.php -h all  >> /dev/null 2>&1

In case you need/want to clean the LibreNMS database:

#!/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
echo REMOVING HOST $line
/opt/librenms/delhost.php $line
done < <(/usr/bin/mysql -uroot -p$1 -B -e "select hostname from devices" librenms)