Email to Event
Subscribe

From OpenNMS

Jump to: navigation, search

Contents

Overview

With the best will in the world, it is not always possible to have applications (or devices) SNMP aware. You may have a few appliances or applications that still insist on mailing operators when something is wrong. Our OpenNMS installation post-dates many of our applications, and the developers are either long gone, or reluctant to mess with old code. To work around this, I modified send-event.pl (found in $OPENNMS_HOME/bin), to work in concert with a mail relay to convert eMails to OpenNMS events.

There are a number of things that you need to make this work:

  • Appropriate event definitions for the new events that you are about to create.
  • Ability to edit /etc/aliases to pipe inbound email messages into the script.
  • Perl DBI/DBD::postgres.
  • the script itself.

Event definitions

We are going to create a couple of new types of event, a "normal" one (for events that do not generate a notice) and a "major" event for events that should create a notice.

The event file should look something like this:

<events>
 <event>
  <uei>uei.opennms.org/vendor/mycompany/events/infrastructureNormal</uei>
  <event-label>Normal event from infrastructure systems</event-label>
  <descr><p>%parm[#2]%</p></descr>
  <operinstruct>
   <p>No specific instructions are available for this event.</p>
  </operinstruct>
  <logmsg dest='logndisplay'><p>infrastructure: %parm[#1]%.</p></logmsg>
  <severity>Normal</severity>
 </event>
 <event>
  <uei>uei.opennms.org/vendor/mycompany/events/infrastructureMajor</uei>
  <event-label>Major event from infrastructure systems</event-label>
  <descr><p>%parm[#2]%</p></descr>
  <operinstruct>
   <p>No specific instructions are available for this event.</p>
  </operinstruct>
  <logmsg dest='logndisplay'><p>infrastructure: %parm[#1]%.</p></logmsg>
  <severity>Major</severity>
 </event>
</events>

It defines two events, both having two parameters, the first (%parm[#1]) used in the event "title", the second (%parm[#2]%) in the event description (seen when you view the event detail in the webUI). Parm[#1] will be extracted from the email subject, parm[#2] will be extracted from the email body.

You should put this somewhere in $OPENNMS_HOME/etc/events (say Mycompany.events.xml)

You'll need to include this with an <event-file> tag in $OPENNMS_HOME/etc/eventconf.xml thus:

<event-file>/opt/OpenNMS/etc/events/Mycompany.events.xml</event-file>

Obviously if you want to generate, notices, you'll need to configure notifications.xml as well. We want our new infrastructureMajor event to generate a notice. You could also do this via the webUI.

<notification name="Infrastructure Major event" status="on">
 <uei>uei.opennms.org/vendor/mycompany/events/infrastructureMajor</uei>
 <rule>IPADDR IPLIKE *.*.*.*</rule>
 <destinationPath>Infrastructure</destinationPath>
 <text-message>Infrastructure Notice #%noticeid% %interfaceresolve%: %parm[#1]%</text-message>
 <subject>Infrastructure Notice #%noticeid% %parm[#1]%</subject>
</notification>

/etc/aliases

This is the bit that pipes inbound emails into the the perl script.

infrastructure.normal: | /usr/local/scripts/emailtoevent.pl -s opennms1.mycompany.com\,opennms2.mycompany.com -u  
uei.opennms.org/vendor/mycompany/events/infrastructureNormal -i 192.168.1.1
infrastructure.major: | /usr/local/scripts/emailtoevent.pl -s opennms1.mycompany.com\,opennms2.mycompany.com -u 
uei.opennms.org/vendor/mycompany/events/infrastructureMajor -i 192.168.1.1

Anything sent to infrastructure.normal@host.mycompany.com will be sent as an "infrastructureNormal" event (the -u option) to the opernnms instances at opennms1.mycompany.com and opennms2.mycompany.com (the -s option). The -i option defines an IP address to use as the interface for that notice (if none can be gathered from inbound mail). Note that multiple destination hosts need to be comma separated, and that comma needs to be escaped by a backslash.

Perl DBI/DBD::postgres

You're on your own with this, I'm afraid. As ever, CPAN is your friend. You could skip this bit, but you'd need to modify the script accordingly.

The script

I put it in /usr/local/scripts, though you could put it anywhere, so long as you put the correct path in /etc/aliases and it's executeable by your mail runtime user. Obviously it needs to be on the same host as the mail relay.

The script takes three parameters

  • -s - followed by one or more destination OpenNMS instances.
  • -u - a valid event UEI - most likely one you've just configured as above.
  • -i - a default interface for the event.

The script will try and figure out the interface that generated the event from the eMail "from", it then looks at the OpenNMS database to find the nodeid for that interface. Thus if I send an email with a from address of badapp@brokenserver.mycompany.com, the script will try to use the IP address of brokenserver.mycompany.com. If it cannot resolve brokenserver.mycompany.com, it will use the default supplied by the -i option. This little hack makes sure that wherever possible, within OpenNMS, the event is properly associated with the node that generated it.

Please read the script first. As it says, it's a horrible hack, but it works for me ;-)

Perlmongers will notice my totally random use of lexical and global variables. That's lazyness for you.

#!/usr/local/perl-5.8.0/bin/perl

# Horrible hack to punt a mail message into opennms 

use DBI;
use IO::Socket;
use POSIX qw(strftime);
use Getopt::Std;

# Edit this line as appropriate 

$errorlog =  "/var/spool/exim/log/mailtoeventlog"; 

getopt('sui',\%opts);

# Set default vaules for options
# Edit these lines as appropriate

$opts{s} = "opennms1.mycompany.com,opennms2.mycompany.com" if ! defined($opts{s});
$opts{i} = "192.168.1.1" if ! defined($opts{i});
$opts{u} = "uei.opennms.org/vendor/mycompany/events/infrastructureNormal" if ! defined($opts{u});
@targets = split /,/, $opts{s};
open STDERR, ">>$errorlog";

# Process input mail message

while (<STDIN>) {
        chomp;
        $hostfrom = $1 if /^From:[^@]+@([A-Za-z0-9\.\-]*)/;
        $SUBJECT = $1 if /^[sS]ubject:\s(.*)/;
        last unless /\S/;
}
while (<STDIN>) {
        $MESSAGE .= $_;
}

$hostfrom =~ s/backup-(.*)/$1/;

if ( @addresses = gethostbyname($hostfrom) ) {
        @addresses = map { inet_ntoa($_) } @addresses[4 .. $#addresses];
        $interface = $addresses[0];
        }
else {
        $interface = $opts{i};
}


$PORT_TO = 5817;
$ZONE    = strftime("%Z", localtime);

push(@PARMS, { name => subject, value => $SUBJECT });
push(@PARMS, { name => message, value => $MESSAGE });

my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
$year += 1900;
my $month = $mon;
$min   = sprintf("%02d", $min);
$sec   = sprintf("%02d", $sec);
my $ap = ($hour >= 12) ? "PM" : "AM";
$ap    = "PM" if ($hour >= 12);
$hour  = $hour % 12;
my @week = ('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
my @month = ('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October',  
'November', 'December');

my $event_head = <<END;

<log>
 <events>
  <event>
   <source>mail-to-event</source>
   <host>$HOSTNAME</host>
   <time>$week[$wday], $month[$month] $mday, $year $hour:$min:$sec $ap $ZONE</time>
   <uei>$opts{u}</uei>
END

if (@PARMS) {
  $event_parms .= "     <parms>\n";
  for my $parm (@PARMS) {
    $event_parms .= <<END;
    <parm>
     <parmName><![CDATA[$parm->{'name'}]]></parmName>
        <value type="string" encoding="text"><![CDATA[$parm->{'value'}]]></value>
    </parm>
END
  }
  $event_parms .= "     </parms>\n";
}


$event_foot .= <<END;
  </event>
 </events>
</log>
END

foreach $target (@targets) {
        $target =~ s/[\\]//gs;
        my $dbh = DBI->connect("dbi:Pg:dbname=opennms;host=$target;port=5432","opennms","opennms");
        if ($DBI::err) {
            print STDERR "unable to connect to postgres on $target $DBI::errstr\n";
            print STDERR "unable to send event $opts{u} to $target\n";
            next;
        }
        my $nodeid = $dbh->selectrow_array(qq{SELECT nodeid from ipinterface WHERE ipaddr LIKE '$interface'});
        if (! defined($nodeid) ) {
            print STDERR "no nodeid for the given interface $interface\n";
            $interface = $opts{i};
            print STDERR "using default interface $opts{i} to lookup nodeid\n";
            $nodeid = $dbh->selectrow_array(qq{SELECT nodeid from ipinterface WHERE ipaddr LIKE '$interface'});
        }
        my $rc = $dbh->disconnect;
        print STDERR "$week[$wday], $month[$month] $mday, $year $hour:$min:$sec $ap $ZONE : ";
        if (my $socket = IO::Socket::INET->new(PeerAddr => $target, PeerPort => $PORT_TO, Proto => "tcp", Type =>  
SOCK_STREAM)) {
                print $socket $event_head;
                print $socket "<interface>$interface</interface>\n";
                print $socket "<nodeid>$nodeid</nodeid>\n";
                print $socket $event_parms if ( defined ($event_parms) );
                print $socket $event_foot;
                $socket->close();
                print STDERR  "sent $opts{u} to $target\n";
        } else {
                print STDERR "Couldn't connect to $target:$PORT_TO \n";
        }
} 

exit 0;