#! /usr/bin/perl -w

# Copyright 2008-2014 Cumulus Systems Incorporated.
# All Rights Reserved.

# This script should be run every 15 minutes from a CRON entry. Each time it runs, it calls data collection script.
# Each one is invoked with a time-out. So if script hangs, we kill it and keep going.

# Switch on warnings. It is used so that we can switch-off warning in Getopts() call.
$^W = 1;

use File::Path;

# Used to read script arguments.
use Getopt::Std;

# It is required for logging information.
use commonLogModule;

# This variable stores the probe type.
my $probeType = "Linux";

# This variable stores commonLogModule instance.
my $logObj = "";

# Time out in seconds. Each program killed if it doesn't finish within this time.
my $TIMEOUT = 30;

# This variable is used to store the return status of a function call.
my $status = 0;

# Installation path of our code.
my $installDir = "";

# Path of directory where raw data will be dumped.
my $dataDir = "";

# Path of directory where configuration file present.
my $confDir = "";

# Path of directory where raw files will be stored for second iteration to send.
my $retryDir = "";

# Size limit for retry directory in MB.
my $retryDirSizeLimit = 25;

# This variable stores IP of this machine.
my $ipOfThisMachine = "";

# Initialize the environment.
$status = init();
if (0 != $status)
{
    $logObj->error("Call to init() failed.");
    goto EXIT;
}

# Check size of retry directory.
$status = checkRetryDirectoryStatus();
if (0 != $status)
{
    $logObj->error("Call to checkRetryDirectoryStatus() failed.");
    goto EXIT;
}

# Main logic.
$status = main();
if (0 != $status)
{
    $logObj->error("Call to main() failed.");
    goto EXIT;
}

EXIT:

$logObj->info("Return status: [$status].");

exit $status;

# This function is used to initialize the environment to run the Perl script correctly.
#
# @affected global variables :
#   $installDir
#   $retryDirSizeLimit
#   $dataDir
#   $confDir
#   $retryDir
#   $logObj
#
# @return :
#   0 if Success
#   1 if Error
sub init
{
    # This variable is used to store the return value of function calls.
    my $retVal = 0;

    # This variable stores Log folder path.
    my $logFolder = "";

    # If first argument is not an option then it is an error.
    if (@ARGV && $ARGV[0] =~ "^-.+" )
    {
        # Suppress annoying undef warnings.
        local($^W) = 0;

        # Read option arguments.
        &getopts("s:u:m:d:");
    }

    if (!defined($::opt_d))
    {
        die "[triggerDataCollection.pl][ERROR]: -d option is not provided for installation directory."
    }

    if (!defined($::opt_s))
    {
        die "[triggerDataCollection.pl][ERROR]: -s option is not provided for backlog files size limit."
    }

    # Get IP address of this machine.
    $ipOfThisMachine = `/sbin/ifconfig eth0 | sed -n '/.*inet .*dr:/{;s/.*dr://;s/ .*//;p;}'`;

    chomp($ipOfThisMachine);

    if ($ipOfThisMachine !~ /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/)
    {
        $ipOfThisMachine = getNonLoopBackIp();
        chomp($ipOfThisMachine);
    }
    
    $installDir = $::opt_d;

    $logFolder = $installDir."/logs";

    # If logFolder folder does not exist then create it.
    if (! (-e $logFolder))
    {
        # Create log folder.
        unless (defined eval {mkpath($logFolder)})
        {
            $retVal = 1;

            goto EXIT;
        }
    }

    # Get commonLogModule instance.
    $logObj = commonLogModule->getInstance($logFolder, $probeType, $ipOfThisMachine);

    $retryDirSizeLimit = $::opt_s;
    $logObj->debug("Inout retry directory size: [$retryDirSizeLimit].");

    $dataDir = $installDir."/data";

    # If data folder does not exist then create it.
    if (! (-e $dataDir))
    {
        # Create data folder.
        unless (defined eval {mkpath($dataDir)})
        {
            $logObj->error("Not able to create folder: [$dataDir].");
            $retVal = 1;

            goto EXIT;
        }
    }

    $confDir = $installDir."/conf";

    # If confDir folder does not exist then create it.
    if (! (-e $confDir))
    {
        # Create conf folder.
        unless (defined eval {mkpath($confDir)})
        {
            $logObj->error("Not able to create folder: [$confDir].");
            $retVal = 1;

            goto EXIT;
        }
    }

    $retryDir = $dataDir."/retry";

    # If retryDir folder does not exist then create it.
    if (! (-e $retryDir))
    {
        # Create retry folder.
        unless (defined eval {mkpath($retryDir)})
        {
            $logObj->error("Not able to create folder: [$retryDir].");
            $retVal = 1;

            goto EXIT;
        }
    }

EXIT:

    return $retVal;
}

# This function is used to check whether retry directory limit exceeded or not.
#
# @affected global variables :
#
# @return :
#   0 if Success
#   1 if Error
sub checkRetryDirectoryStatus
{
    # This variable is used to store the return value of function calls.
    my $retVal = 0;

    # If number of backlog files exceeds given criteria then quit.
    if (-d $retryDir)
    {
        my $output = `du -sbh $retryDir`;

        $logObj->debug("Output of command [`du -sbh $retryDir`] is [$output].");

        # Current size of retry directory.
        my $retryDirSize = 0;

        # Parse command output to get size of retry directory in MB.
        if ($output =~ /^(.*)(M)(.*)$/)
        {
            $retryDirSize = $1;
        }

        $logObj->info("Current size of [$retryDir] directory is [$retryDirSize] MB.");

        # If current size of retry directory exceeds given criteria then exit.
        if ($retryDirSize >= $retryDirSizeLimit)
        {
            $logObj->info("Exiting as retry directory reached maximum size criteria.");
            $retVal = 1;

            goto EXIT;
        }
    }

EXIT:

    return $retVal;
}

# This function is used to call actual data collection scripts. This function also add HOSTNAME variable in system environment which is used later
# in data collection scripts.
#
# @return :
#   0 if Success
#   1 if Error
sub main
{
    # This variable is used to store the return value of function calls.
    my $retVal = 0;

    # Change PATH variable to include sbin folder for ifconfig command.
    $ENV{'PATH'} = ".:$installDir/bin:/usr/bin:/bin:/sbin:".$ENV{'PATH'};

    # Push host-name into environment so scripts can get it.
    my $HOSTNAME = `hostname`;
    chomp($HOSTNAME);

    if ($HOSTNAME =~ /^localhost/)
    {
        # Appending IP address to localhost.
        $HOSTNAME .= "_$ipOfThisMachine";
    }

    # Set environment variable for HOSTNAME.
    $ENV{'HOSTNAME'} = $HOSTNAME;

    # Change directory to "bin".
    if (!chdir($installDir."/bin"))
    {
        $logObj->fatal("Can't chdir into $installDir/bin: [$!]");
        $retVal = 1;

        goto EXIT;
    }

    # Array of data collection script to run.
    my @scriptsToRun = ("$installDir/lib/collectIoStats.sh",
                        "$installDir/lib/collectMpStats.sh",
                        "$installDir/lib/collectNetStats.sh",
                        "$installDir/lib/collectVmStats.sh",
                        "$installDir/lib/collectConfInfo.sh");

    # Run scripts.
    foreach (@scriptsToRun)
    {
        $logObj->debug("Before executing script: [$_].");

        # Run script if it exist.
        if (-e $_)
        {
            $retVal = runCommandWithTimeout($_." ".$dataDir." ".$confDir ." &", $TIMEOUT);
            if ($retVal == 1)
            {
                $logObj->error("Failed to execute script: [$_].");
                goto EXIT;
            }
        }
        else
        {
            $logObj->warn("Script [$_] not exist.");
        }

        $logObj->debug("After  executing script: [$_].");
    }

EXIT:

    return $retVal;
}

# This function executes input command with a given time-out value.
#
# @param:
#     $_[0] - [In] Command to execute.
#     $_[1] - [In] Time out value for the command execution.
#
# @return:
#     0 if Success
#     1 if Error
sub runCommandWithTimeout
{
    my ($cmd, $timeout) = @_;

    my $retval = 0;

    eval
    {
        # If there is time out in file send operation then eval will set $@ with "alarm".
        local $SIG{ALRM} = sub { die "alarm\n" };

        # Set the time out for file send operation.
        alarm($timeout);

        $retval = system($cmd);

        # Cancel the alarm.
        alarm(0);
    };

    # $@ gives error message from the last eval command.
    if ($@)
    {
        if ($@ ne "alarm\n")
        {
            logMsg("Error while running the command. Command: [$cmd]. Error: [$@].");
            $retval = 1;
        }
        else
        {
            logMsg("Time out in command execution. Command: [$cmd].");
            $retval = 1;
        }
    }

    return $retval;
}

# This function is used to get the Loopback IP address
#
# @param:
#     None
#
# @return:
#     Loopback IP address list.
sub getLoopBackIps
{
    # Get ifconfig output.
    my $ifconfig = `/sbin/ifconfig -a`;

    # Used to decide the line which contain IP address.
    my $readNextLine = 0;

    # This list is used to store complete line having IP address.
    my @outArray = ();

    # Output list which contain Loopback IP address.
    my @outIpList = ();

    # Split output for each line.
    for (split(/\n/, $ifconfig))
    {
        # If current line is empty then go to next line.
        if (/^$/)
        {
            next;
        }

        # If current line doesn't have "Loopback" string or exact "inet" string (no "inet6") then go to next line.
        if ($_ !~ /Loopback|\binet\b/)
        {
            next;
        }

        # This line has IP address.
        if ($readNextLine == 1)
        {
            push(@outArray, $_);
        }

        # If current line has "Loopback" string then means that next line will have IP address.
        if (~/Loopback$/)
        {
            $readNextLine = 1;
        }
        # If current line has no "Loopback" string then means that we already found IP address.
        else
        {
            $readNextLine = 0;
        }
    }

    # Use only those lines where "inet" is at the start of line.
    my @ipArray = grep {/^\s*inet/} @outArray;

    foreach (@ipArray)
    {
        # Find IP address and push in output list.
        if (m/addr:(.*)\s+M/)
        {
            push(@outIpList, $1);
        }
    }

    return @outIpList;
}

# This function is used to return an IP address which is not a loopback IP.
#
# @param
#    None
#
# @return
#    IP address which is not loopback IP.
sub getNonLoopBackIp
{
    # Get IP address list as a single string.
    my $ifconf = `/sbin/ifconfig | sed -n '/.*inet .*dr:/{;s/.*dr://;s/ .*//;p;}'`;

    # Get IP addresses in a list.
    my @ips = split(/\n/, $ifconf);

    # Get the list of loopback IPs.
    my @loopBackIps = getLoopBackIps();

    # Loop for all IPs.
    for (my $i = 0; $i <= $#ips; $i++)
    {
        # Loop for all loopback IPs.
        foreach my $loopBackaddr (@loopBackIps)
        {
            # If we found any IP which is not a loopback IP then return that IP.
            if ($ips[$i] ne $loopBackaddr)
            {
                return $ips[$i];
            }
        }
    }

    return undef;
}
