#!/usr/bin/perl

# Runs a probe.

use strict;

use Getopt::Long;
use Error ':try';

use NOCpulse::Config;
use NOCpulse::Log::LogManager;
use NOCpulse::Probe::Config::Command;
use NOCpulse::Probe::Config::CommandParameter;
use NOCpulse::Probe::Config::ProbeRecord;
use NOCpulse::Probe::ModuleMap;
use NOCpulse::Probe::ProbeRunner;

my %PREDEFINED_ARGS = ('--help'    => '',
                       '--probe'   => 'i',
                       '--log'     => 's%',
                       '--debug'   => 'i',
                       '--live'    => '',
                       '--module'  => 's');

# Allow single or double dashes.
Getopt::Long::Configure('prefix_pattern=--|-');

# Check to verify that the user running runprobe.pl is nocpulse and exit if not
my $id = `id`;
$id =~ /^uid=(\d+)(.*)/;

if (($1 == 0) || ($2 !~ /nocpulse/)) {
    print "Please 'su - nocpulse' to run this program as uid $1 may not run /opt/home/nocpulse/bin/runprobe.pl.\n";
    exit(0);
}

my $probe_records = load_config_db();

my %args = process_args($probe_records);
if ($args{help}) {
    print usage();
    exit(0);
}
my $probe_rec   = find_probe_rec($probe_records, %args);
my $command     = find_command($probe_rec, %args);
my $perl_module = find_module($command, %args);

# Make the default behavior of the script to run in test mode,
# not enqueueing ts/scdb data or sending notifications
# unless the user specifically uses the --live option
if (!$args{live}) {
    $args{test} = 'test';
}

my $runner = NOCpulse::Probe::ProbeRunner->new($probe_rec, $command, $perl_module, $args{test});

NOCpulse::Log::LogManager->instance->ensure_level(ref($runner), 1);

$runner->run();


# Loads command database and probe record database, and returns the
# probe records array ref. Probe records do not currently manage their
# own instances to avoid double storage with the existing framework's
# NPRecords mechansims.
sub load_config_db {
    my $config = NOCpulse::Config->new();
    NOCpulse::Probe::Config::Command->load($config->get('netsaint', 'commandParameterDatabase'));
    return Storable::retrieve($config->get('netsaint', 'probeRecordDatabase'));
}

# Returns the name of the module to run. Uses the 'module' command-line
# argument if present, or the ModuleMap mapping of the command's
# command_class value.
sub find_module {
    my ($command, %args) = @_;

    return $args{module} || 
      NOCpulse::Probe::ModuleMap->instance->module_for($command->command_class);
}

# Returns the probe record for the probe ID.
# Overrides probe config parameters with matching arguments
# from the command line.
sub find_probe_rec {
    my ($probe_records, %args) = @_;

    my $probe_rec_hash;

    my $probe_id = $args{probe};

    if ($probe_id) {
        $probe_rec_hash = $probe_records->{$probe_id}
          or bail("\nCannot find a probe with ID $probe_id.\n");
    } else {
        # Create a dummy probe record.
        $probe_rec_hash = {recid               => -1, 
                           probe_type          => 'ServiceProbe',
                           parameters          => { },
                           contact_groups      => [],
                           contact_group_names => [],
                          };
    }

    my $probe_rec = NOCpulse::Probe::Config::ProbeRecord->new($probe_rec_hash);
    assign_overridden_params($probe_rec, %args);

    return $probe_rec;
}

# Finds the command object for this probe.
sub find_command {
    my ($probe_rec, %args) = @_;

    my $command;

    if ($args{probe}) {
        $command = NOCpulse::Probe::Config::Command->instances($probe_rec->command_id)
          or bail("\nCannot find a command record for probe '", $probe_rec->recid,
                  "' or module '$args{module}'\n");
    }
    $command ||= NOCpulse::Probe::Config::Command->named($args{module});

    unless ($command) {
        my %params = ();
        while (my ($key, $value) = (each %args)) { 
            my %param_fields = (command_id => -1,
                                param_name => $key,
                                param_type => 'Config',
                               );
            $params{$key} = NOCpulse::Probe::Config::CommandParameter->new(%param_fields);
        }
        $command = NOCpulse::Probe::Config::Command->new(command_id    => -1,
                                                         command_class => $args{module},
                                                         parameters    => \%params);
    }
    return $command;
}

# Assign any command-line override args.
sub assign_overridden_params {
    my ($probe_rec, %args) = @_;

    while (my ($param, $value) = each %args) {
        unless ($PREDEFINED_ARGS{'--'.$param}) {
            $probe_rec->parameters->{$param} = $value;
        }
    }
}

# Print a message and the usage message to STDERR, then exit.
sub bail {
    my @msg = @_;
    print STDERR join('', @msg), "\n", usage();
    exit 1;
}

# Print usage.
sub usage {
    return "
Usage: $0 --probe <probe_id> [--module <perl_module>] [--live]
       [--help] [--log <pkg=level>] [--probe_arg <value>...] [probe_id]
  --probe     run the probe with this ID
  --probe_arg override any probe parameters from the database
  --module    package name of alternate code to run
  --log       sets log level for a package or package prefix
  --debug     sets numeric debugging level
  --live      executes the probe and both enqueues data and sends out notifs(if needed)
  --help      print this message

Any argument not starting with -- is treated as a probe ID,
so to run probe 1234 you can type
  $0 1234
";
}

# Parse and validate the command line. Returns argument hash
# if valid, bails out otherwise.
sub process_args {
    my $probe_records = shift;

    my %args = parse_args();
    if (my $msg = validate_args($probe_records, %args)) {
        bail($msg);
    }
    return %args;
}

# Validate that probe ID is present unless --help or --command requested.
sub validate_args {
    my ($probe_records, %args) = @_;
    
    # Just print the usage message.
    $args{help} and return 0;

    my $probe_id = $args{probe};
    if ($probe_id) {
        my $probe_rec = $probe_records->{$probe_id};
        $probe_rec or return "Cannot find a probe with ID $probe_id.\n";
        $probe_rec = NOCpulse::Probe::Config::ProbeRecord->new($probe_rec);
        my $params = $probe_rec->parameters;
        # Verify that the probe args are valid.
        foreach my $key (keys %args) {
            unless (exists $params->{$key} or exists $PREDEFINED_ARGS{'--'.$key}) {
                return "\nParameter $key not valid for this command; possible parameters are " .
                  "\n\t" . join("\n\t", sort keys %$params) . "\n";
            }
        }
    } elsif (!$args{module}) {
        return "\nYou must enter either a probe ID or a Perl module name.\n";
    }

    return;
}

# Parse command line.
sub parse_args {
    my %args = ();
    my @allowed = map {
        $PREDEFINED_ARGS{$_} ? "$_=$PREDEFINED_ARGS{$_}": $_
    } keys %PREDEFINED_ARGS;

    # Generate the list based on what's been entered.
    # We can check the args once the probe ID is known.
    foreach my $arg (@ARGV) {
        if ($arg =~ /(--?\w+)/) {
            push(@allowed, $1.':s') unless exists $PREDEFINED_ARGS{$1};
        }
    }
    GetOptions(\%args, @allowed) or bail();

    # Treat any leftover value as the probe ID.
    $args{probe} ||= @ARGV[0] if @ARGV;

    NOCpulse::Log::LogManager->instance->configure(%{$args{log}});

    if (exists $args{debug}) {
        NOCpulse::Log::LogManager->instance->output_handler->level($args{debug});
    }

    return %args;
}



=head1 NAME

rhn-runprobe - runs a configured probe on a RHN Monitoring Scout

=head1 SYNOPSIS


=head1 DESCRIPTION

This script will run a configured probe on the current scout in test mode.
This can be a helpful debugging tool for any problems relating the execution
of probes on this scout. In the default test mode, the probe itself will execute,
but the metric date, probe state and notification alerts that would normally 
be generated and saved to disk are supressed. 


=head1 OPTIONS

=item --probe 

=item --log

=item --live

=item --probe_arg

=item --debug

=item --module

=item --help



=head1 EXAMPLES

	rhn-runprobe 1234

	rhn-runprobe --live 1234

	rhn-runprobe --log=all=4 1234

	rhn-runprobe --log=NOCpulse::Probe=2 1234
	
	rhn-runprobe --warning=20 1234

	rhn-runprobe --timeout=2 --live --log=all=4 1234


=head1 NOTES

=head1 RESTRICTIONS

This script should only be run as the B<nocpulse> user.

=head1 AUTHORS

Rod McChesney <rod@nocpulse.com>  - wrote most of original code during the
refactoring of the Probe Framework in mid-2002.

Nick Hansen <nhansen@redhat.com> - added a few lame things, including this man page




