#!/usr/bin/perl
#=======================================================================================
# DELETE-STALE-DEVICES
#=======================================================================================
# delete stale NeDi devices in NeDi database
#
# ERRORS:
#
# CHANGELOG:
# 2017_04_24 1.0.0 Initial version
# 2020_11_26 1.2.0 First working version since split of script
#
# TODO: help
# Update help text
#
#=======================================================================================
# EXAMPLES
#=======================================================================================
# delete-stale-devices --help
# delete-stale-devices --age 30d -v
# delete-stale-devices --age 30d --no-dryrun
#=======================================================================================
# ENVIRONMENT
#=======================================================================================
#=======================================================================================
# PRAGMAS
#=======================================================================================
use strict;
use warnings;

# LIBS
use FindBin;
use lib '/usr/lib/monitos/plugins/perl/lib';

# Load required Perl MODULES
use Getopt::Long qw(:config no_ignore_case bundling);
use Pod::Usage;
use MOSUtils;
use Data::Dumper;
$Data::Dumper::Indent = 1;
$Data::Dumper::Terse = 1;
$Data::Dumper::Pair = ' => ';
# use JSON

use constant {
    STD   => 0,
    INF   => 1,
    DBG   => 2,
    SPM   => 3,
    BREAK => "\n",
    BLANK => ' ',
    OK    => 0,
    WARNING    => 1,
    CRITICAL   => 2,
    UNKNOWN    => 3,
};

# Get the base name of this script for use in the examples
use File::Basename;
my $PROGNAME = basename($0);

my ( $sec, $min, $hour, $day, $month, $year, $wday, $yday, $isdst ) = localtime;
my $date = sprintf( "%04d_%02d_%02d", $year + 1900, $month + 1, $day );
my $time = sprintf( "%02d_%02d_%02d", $hour,        $min,       $sec );

#=======================================================================================
# VARIABLES
#=======================================================================================
my $VERSIONNR = '1.2.1';
my $BUILD     = '362';   
my $YEAR      = '2020';

#=======================================================================================
# Define and document the valid command line options
# usage, help, version, timeout and verbose are defined by default.
# GETOPTIONS
#=======================================================================================
my $opt_help         = 0;
my $opt_version      = 0;
my $opt_verbose      = 0;
my $opt_logfile      = '';
my $opt_timeout      = 30;
my $opt_usage        = 0;
# delete stale devices
my $opt_delete_stale_devices = 1;
my $opt_age         = '30d';
my $opt_dryrun      = 1;
my $opt_dbhost      = 'localhost';
my $opt_dbname      = 'nedi';
my $opt_dbuser      = 'nedi';
my $opt_dbpass      = 'dbpa55';
my $opt_filter;

GetOptions(
    'help|h'        => \$opt_help,
    'verbose|v+'    => \$opt_verbose,
    'logfile|l=s'   => \$opt_logfile,
    'version|V'     => \$opt_version,
    'timeout|t=i'   => \$opt_timeout,
    'usage|?'       => \$opt_usage,
# change this
    'age|A=s'       => \$opt_age,
    'dryrun!'       => \$opt_dryrun,
) 
or pod2usage("Try '$0 --help' for more information.");


#=======================================================================================
# Perform sanity checking on command line options
#=======================================================================================
pod2usage( -exitvalue => 3, -verbose => 2 )
    if $opt_help;
pod2usage( -exitvalue => 3, -verbose => 99, -sections => "NAME", -output => \*STDOUT, -message => "$0:\nMonitos Plugin\nVersion $VERSIONNR\n$BUILD\n(C) $YEAR Freicon GmbH & Co. KG")
    if $opt_version;
pod2usage( -exitvalue => 3, -verbose => 99, -sections => "SYNOPSIS", -output => \*STDOUT )
    if $opt_usage;
#pod2usage( -exitvalue => 3, -verbose => 1, -output => \*STDOUT, -message => "$0: Missing argument monitos authtoken.\n")
    #if ( $opt_token eq 'NOTSETYET' && ( $opt_mode ne 'attributes' ) );


#=======================================================================================
# PROLOG
#=======================================================================================
$MOSUtils::VERBOSE = $opt_verbose;
$MOSUtils::LOGFILE = $opt_logfile;
#logger( INF, 'INF is active. Verbose is: '.$opt_verbose);
logger( DBG, 'DBG is active. Verbose is: '.$opt_verbose);
logger( SPM, 'SPM is active. Verbose is: '.$opt_verbose);

# global
use vars qw($dbh);


#=======================================================================================
# Activate timeout.
# THIS is where you'd do your actual checking to get a real value for $result
# Don't forget to timeout after $p->opts->timeout seconds, if applicable.
#=======================================================================================
logger( DBG, "Timeout is: " . $opt_timeout );
$SIG{ALRM} = sub {
    die ( "Plugin $PROGNAME reached timeout of " . $opt_timeout . ' seconds' );
};

alarm( $opt_timeout );


#=======================================================================================
# MAIN
# THIS is where you'd do your actual checking to get a real value for $checkresult
#=======================================================================================
if ($opt_delete_stale_devices) {
    mos_exit( 'UNKNOWN', 'Could not connect to database '.$opt_dbname) unless $dbh = db_connect($opt_dbname);
    my $timespan   = get_firstdis($opt_age); 
    $opt_filter = 'lastdis <= '.$timespan;
    logger(DBG, 'Filter is: '.$opt_filter);

    my $nedi_devices = get_nedi_devices($opt_filter);
    logger(DBG, 'Found '.scalar( @{$nedi_devices}).' devices in db "'.$opt_dbname.'"');
    if (scalar(@{$nedi_devices}) == 0) {
        logger(STD, 'Found no devices in db using filter: '.$opt_filter);
        exit 3;
    }
    logger(SPM, 'NeDi devices are: '.BREAK.Dumper($nedi_devices));

    for my $d (@{$nedi_devices}) {
        my $device_name = $d->{'device'};
        logger(STD, 'Device_name is: '.$device_name);
        my $nr = delete_device($device_name);
        if ($nr) {
            logger(STD, 'Deleted device '.$device_name.' with '.$nr.' entrie(s)!') unless $opt_dryrun;
        }
        else {
            logger(STD, 'No entries deleted for device '.$device_name.'!') unless $opt_dryrun;
        }
    }
    logger(STD, 'Deleted '.scalar(@{$nedi_devices}).' devices in db using filter: '.$opt_filter) unless $opt_dryrun;
    exit 0;
}

#=======================================================================================
# END OF MAIN
#=======================================================================================
# ALARM STOP
alarm(0);

# EXIT - we never should come here
mos_exit('UNKNOWN', 'Plugin exits here - something went terribly wrong');


#=======================================================================================
# SUBPROCEDURES
#=======================================================================================
#=============================
# SUB db_connect
#=============================
sub db_connect {
    eval {
        require DBI;
    };
    die('Could not load module DBI') if $@;
    my $dbh = DBI->connect(
        "DBI:mysql:$opt_dbname:$opt_dbhost",
        "$opt_dbuser",
        "$opt_dbpass",
        {
            RaiseError => 1,
            AutoCommit => 1
            #AutoCommit => 0
        }
    );
    return $dbh;
}

#=============================
# SUB get_nedi_devices
# FROM mosimporter
# select *, inet_ntoa(nodip) from nodarp where mac="848f69af3a5c"\G
#=============================
sub get_nedi_devices {
    my ( $filter ) = @_;
    my @devices;

    my $sql = 'select * from devices';
    $sql   .= BLANK.'WHERE'.BLANK.$filter if lc($filter) ne 'all';

    logger(DBG, 'sql is: ***'.$sql.'***');
    my $sth = $dbh->prepare( $sql );
    $sth->execute();

    my $num_rows = $sth->rows;
    logger(DBG, 'Could not find a matching device within database "'.$opt_dbname.'"') unless $num_rows;

    logger(DBG, "Number of devices is: " . $num_rows );

    while ( my $dev = $sth->fetchrow_hashref ) {
        my $output = 'device:  '.$dev->{'device'}.' ip: '.$dev->{'devip'};
        logger(DBG, $output );
        push( @devices, $dev );
    }
    # END while
    $sth->finish();
    #$dbh->disconnect();
    return \@devices;
}

#=============================
# SUB delete_device
# ARG1: device_name
# RETURN: 
#=============================
sub delete_device {
    my ($device) = @_;
    logger(DBG, 'device_name is '.$device);

    my $rows_deleted = 0;
    my $deleted = 0;
    if ($opt_dryrun) {
        logger(STD, 'Dryrun is active. Nothing to do!');
        return 0;
    }
    else {
        $deleted = $dbh->do(q{DELETE FROM devices WHERE device = ?  }, undef, $device) or die $dbh->errstr;
        $rows_deleted += $deleted;
        $deleted = $dbh->do(q{DELETE FROM interfaces WHERE device = ?  }, undef, $device);
        $rows_deleted += $deleted;
        $deleted = $dbh->do(q{DELETE FROM modules WHERE device = ?  }, undef, $device);
        $rows_deleted += $deleted;
        $deleted = $dbh->do(q{DELETE FROM links WHERE device = ?  }, undef, $device);
        $rows_deleted += $deleted;
        $deleted = $dbh->do(q{DELETE FROM links WHERE neighbor = ?  }, undef, $device);
        $rows_deleted += $deleted;
        $deleted = $dbh->do(q{DELETE FROM configs WHERE device = ?  }, undef, $device) or die $dbh->errstr;
        $rows_deleted += $deleted;
        logger(STD, 'Could not find a matching device within database "'.$opt_dbname.'"') if ($rows_deleted == 0);
        return $rows_deleted;
    }
}

#=============================
# SUB get_firstdis / calculateAge
# FROM mosimporter
# Input: option $optAge
# Output: $age in seconds
#=============================
sub get_firstdis {
    my ($optAge) = @_;
    my $now = time();
    my $age;
    logger(DBG, "age is $optAge" );
    if ( $optAge =~ s/d$//i ) {
        logger(DBG, "age is in days" );
        $age = int($optAge) * 86400;
        logger(DBG, "age is $age" );
    }
    elsif ( $optAge =~ s/h$//i ) {
        logger(DBG, "age is in hours" );
        $age = $optAge * 3600;
        logger(DBG, "age is $age" );
    }
    elsif ( $optAge =~ s/m$//i ) {
        logger(DBG, "age is in minutes" );
        $age = $optAge * 60;
        logger(DBG, "age is $age" );
    }
    elsif ( $optAge =~ s/s$//i ) {
        logger(DBG, "age is in sec" );
        $age = int $optAge;
        logger(DBG, "age is $age" );
    }
    else {
        logger(DBG, "age is in sec" );
        $age = int $optAge;
        logger(DBG, "age is $age" );
    }
    return $now - $age;
}

__END__

#=======================================================================================
# PERLDOC
#=======================================================================================
=head1 NAME

delete-stale-devices - delete stale devices in NeDi database



=head1 VERSION

This document corresponds to version 1.0.0 of this script.



=head1 SYNOPSIS

delete-stale-devices [--help] [--version | -V] [--age | -A <AGE>] [-v][v][v]

Delete stale devices in NeDi database (lastseen (Unix timestamp) is lower than given age)



=head1 DESCRIPTION

delete-stale-devices --age 30d --dry-run [-v][v][v]



=head1 OPTIONS

=over 4

=item B<-A | --age >

Given age of devices which should be considered as stale

=item B<--dryrun >

Dont deleted actually devices. Just show list of stale devices. Dryrun is the default. Delete old devices with --no-dryrun

=item B<-t | --timeout < SECONDS >>

Timeout for execution in seconds

=back


=head1 Examples

=over 4

=item B<delete-stale-devices --help>

Show the plugin help

=item B<delete-stale-devices --age 30d -v >

Show list of stale devices. Not deleting because of dryrun

=item B< delete-stale-devices --age 30d --no-dryrun >

Delete stale devices with given age. 

=back


=head1 AUTHOR

studo


=head1 LICENSE AND COPYRIGHT

2020 studo

