#!/usr/bin/perl
# vim:ts=4 sw=4 ft=perl et:

use strict;
use warnings;
use Getopt::Long;
use LWP::UserAgent;
use LWP::Simple;
use MogileFS::Client;

# Written by Justin Brehm (jbrehm@icontact.com) - 05/07/2008 14:09:00
# 
# check_mogile.pl - Nagios Plugin to check the functionality of MogileFS
# Requirements - mysql-client 

my %ERRORS=('OK'=>0, 'WARNING'=>1, 'CRITICAL'=>2, 'UNKNOWN'=>3);
my %opts=('trackers'=> 'localhost:7001', 'domain'=>'default', 'dbhost'=> 'mysqldb001.example', 'db' => 'mogilefs', 'dbuser' => 'mogilefs', 'dbpass' => 'lolwut');

my $host = `uname -n`;
$host =~ s/[\r\n]//g;
my $test_key = "test_" . $host . "_" . time();

my $test_file = "/etc/mogilefs/mogilefs.conf"; # file that we are going to inject/extract 
unless ( -e $test_file ) {
    print "MOGILEFS UNKNOWN - File $test_file doesn't exist.  Please create and try again\n"; 
    exit($ERRORS{"UNKNOWN"});
}

my $mysql = '/usr/bin/mysql'; # specify path to mysql client binary
unless ( -X $mysql ) {
    print "MOGILEFS UNKNOWN - $mysql isn't executible or doesn't exist\n";
    exit($ERRORS{"UNKNOWN"});
}

abortWithUsage() unless
    GetOptions(
        't|trackers=s'    => \$opts{trackers},
        'd|domain=s'      => \$opts{domain},
        'host|dbhost=s'      => \$opts{dbhost},
        'db=s'          => \$opts{db},
        'u|dbuser=s'      => \$opts{dbuser},
        'p|dbpass=s'      => \$opts{dbpass},
        );

abortWithUsage() unless 
    ($opts{trackers} && $opts{domain} && $opts{db} && $opts{dbuser} && $opts{dbpass} && $opts{dbhost});

# Verify that the MySQL database is available first
my $mysql_check = `$mysql --user=$opts{dbuser} --password=$opts{dbpass} --host=$opts{dbhost} $opts{db} -e 'select 1' 2>/dev/null`;
if (!$mysql_check) {
    print "MOGILEFS CRITICAL - MogileFS database $opts{db} is unavailable\n";
    exit($ERRORS{"CRITICAL"});
}

my $mogfs = get_mogfs();
my $in_bytes = inject();
my $out_bytes = extract();
cleanup(); # Delete the file from MogileFS

if ($in_bytes eq $out_bytes) {
    print "MOGILEFS OK - Key $test_key inject/extract success\n";
    exit($ERRORS{"OK"});
} else {
    error("Store size: $in_bytes\nRetrieved size: $out_bytes");
}

sub abortWithUsage {
    print "check_mogile.pl -- Checks the functionality of MogileFS for iContact\n\n";
    print "Usage:\t\$ check_mogile.pl --trackers=127.0.0.1:7001 --domain=publisher [--dbhost=ral-sql002.colo] [--db=mogilefs] [--dbuser=icontact] -[-dbpass=secret]\n\n";
    print "\ttrackers\t: MogileFS trackers and port (127.0.0.1:7001)\n";
    print "\tdomain\t\t: MogileFS domain\n";
    print "\tdbhost\t\t: Hostname/IP of MySQL database\n";
    print "\tdb\t\t: The MogileFS database\n";
    print "\tdbuser\t\t: User with SELECT privileges on database\n";
    print "\tdbpass\t\t: password for user\n";
    exit($ERRORS{"UNKNOWN"});
}

sub error {
    my $err = shift() || "ERROR: no error message provided!";
    my $mogerr = undef;
    if ($mogerr = $mogfs->errstr) {
        $mogerr =~ s/^\s+//;
        $mogerr =~ s/\s+$//;
    }
    my $syserr = undef;
    
    if ($@) {
        $syserr = $@;
        $syserr =~ s/[\r\n]+$//;
    }
    
    print "CRITICAL - ";
    print "$err\n";
    print "MogileFS backend error message: $mogerr\n" if $mogerr;
    print "System error message: $@\n" if $syserr;
    exit($ERRORS{"CRITICAL"});
}

sub get_mogfs {
    my @trackerinput = split(/\s*,\s*/, $opts{trackers});
    my @trackers;
    my %pref_ip;

    foreach my $tracker (@trackerinput) {
        if ($tracker =~ m!(.+)/(.+):(\d+)!) {
            $pref_ip{$2} = $1;
            push @trackers, "$2:$3";
        } else {
            push @trackers, $tracker;
        }
    }
    
    my $handle = MogileFS::Client->new(
            domain => $opts{domain},
            hosts => \@trackers,
            timeout => 3,
            )
        or error("Could not initialize MogileFS");
    $handle->set_pref_ip(\%pref_ip);
    return $handle;
}

# Injects into the default class.  If we want to inject to a specific class
# we'll have to modify this.
sub inject {
    my $src = $test_file;
    my $key = $test_key;

    my $bytes = $mogfs->store_file($key, undef, $src) 
        or error("Injection test failed");

    return $bytes;
}

# Only returns that file size that is being extracted.  To get the full
# file you'd need to return $extractfile instead
sub extract {
    my $key = $test_key;
    my $file;
    
    $file->{type} = 'file';
    $file->{maxnum} = 1;
    $file->{parts}->{1} = {
        paths => [ grep { defined $_ } $mogfs->get_paths($key) ],
    };
    
    unless (scalar(@{$file->{parts}->{1}->{paths}})) {
        error("Extraction test failed");
    }
    
    my $extractfile;
    foreach my $path (@{$file->{parts}->{1}->{paths} || []}) {
        $extractfile .= get($path);
    }
    
    my $size = length $extractfile;
    return $size
}

sub cleanup {
    my $key = $test_key;

    unless ( $mogfs->delete($key) ) {
        error("Cannot delete $key");
    }
}
