#!/usr/bin/perl -w
######################################################################
#
# $Id: webjob-mldbm-get-status,v 1.25 2012/01/07 08:01:23 mavrik Exp $
#
######################################################################
#
# Copyright 2004-2012 The WebJob Project, All Rights Reserved.
#
######################################################################
#
# Purpose: Generate status reports for one or more clients.
#
######################################################################

use strict;
use Fcntl qw(:DEFAULT :flock);
use File::Basename;
use FindBin qw($Bin $RealBin); use lib ("$Bin/../lib/perl5/site_perl", "$RealBin/../lib/perl5/site_perl", "/usr/local/webjob/lib/perl5/site_perl", "/opt/local/webjob/lib/perl5/site_perl");
use Getopt::Std;
use WebJob::MiaRoutines;
use WebJob::MldbmRoutines;
use WebJob::TimeRoutines;
use WebJob::Properties 1.008;

BEGIN
{
  ####################################################################
  #
  # The Properties hash is essentially private. Those parts of the
  # program that wish to access or modify the data in this hash need
  # to call GetProperties() to obtain a reference.
  #
  ####################################################################

  my (%hProperties);

  $hProperties{'CommonRegexes'} = PropertiesGetGlobalRegexes();

  sub GetProperties
  {
    return \%hProperties;
  }
}

######################################################################
#
# Main Routine
#
######################################################################

  ####################################################################
  #
  # Punch in and go to work.
  #
  ####################################################################

  my ($phProperties);

  $phProperties = GetProperties();

  $$phProperties{'Program'} = basename(__FILE__);

  ####################################################################
  #
  # Get Options.
  #
  ####################################################################

  my (%hOptions);

  if (!getopts('ad:j:l:o:qt:', \%hOptions))
  {
    Usage($$phProperties{'Program'});
  }

#FIXME The '-a' option is being phased out.
  ####################################################################
  #
  # The show all flag, '-a', is optional.
  #
  ####################################################################

  $$phProperties{'ShowAll'} = (exists($hOptions{'a'})) ? 1 : 0;

  ####################################################################
  #
  # A database, '-d', is optional.
  #
  ####################################################################

  $$phProperties{'DbFile'} = (exists($hOptions{'d'})) ? $hOptions{'d'} : "/var/webjob/db/mldbm/client.db";

  ####################################################################
  #
  # A job, '-j', is required.
  #
  ####################################################################

  $$phProperties{'Job'} = (exists($hOptions{'j'})) ? $hOptions{'j'} : undef;

  if (!defined($$phProperties{'Job'}))
  {
    Usage($$phProperties{'Program'});
  }

  if ($$phProperties{'Job'} !~ /^$$phProperties{'CommonRegexes'}{'MldbmKeyName'}$/)
  {
    print STDERR "$$phProperties{'Program'}: Job='$$phProperties{'Job'}' Error='Value does not pass muster.'\n";
    exit(2);
  }

  ####################################################################
  #
  # A time limit, '-l', is optional.
  #
  ####################################################################

  $$phProperties{'TimeLimit'} = (exists($hOptions{'l'})) ? $hOptions{'l'} : 0;

  if ($$phProperties{'TimeLimit'} !~ /^$$phProperties{'CommonRegexes'}{'Decimal32Bit'}$/)
  {
    print STDERR "$$phProperties{'Program'}: TimeLimit='$$phProperties{'TimeLimit'}' Error='Value does not pass muster.'\n";
    exit(2);
  }

#FIXME The '-q' option is being phased out.
  ####################################################################
  #
  # The be quiet flag, '-q', is optional.
  #
  ####################################################################

  $$phProperties{'BeQuiet'} = (exists($hOptions{'q'})) ? 1 : 0;

  ####################################################################
  #
  # The option list, '-o', is optional.
  #
  ####################################################################

#FIXME Uncomment 'BeQuiet' and 'ShowAll' once the '-q' and '-a' options have been removed.
# $$phProperties{'BeQuiet'}  = 0;
  $$phProperties{'NoHeader'} = 0;
# $$phProperties{'ShowAll'} = 0;

  $$phProperties{'Options'} = (exists($hOptions{'o'})) ? $hOptions{'o'} : undef;

  if (exists($hOptions{'o'}) && defined($hOptions{'o'}))
  {
    foreach my $sActualOption (split(/,/, $$phProperties{'Options'}))
    {
      foreach my $sTargetOption ('BeQuiet', 'NoHeader', 'ShowAll')
      {
        if ($sActualOption =~ /^$sTargetOption$/i)
        {
          $$phProperties{$sTargetOption} = 1;
        }
      }
    }
  }

  ####################################################################
  #
  # A status type, '-t', is required.
  #
  ####################################################################

  my ($sType);

  $sType = (exists($hOptions{'t'})) ? $hOptions{'t'} : undef;

  if (!defined($sType))
  {
    Usage($$phProperties{'Program'});
  }

  if ($sType !~ /^(?:mia)$/i)
  {
    print STDERR "$$phProperties{'Program'}: Error='Invalid status type ($sType).'\n";
    exit(2);
  }

  ####################################################################
  #
  # If there are no arguments left, process all clients in the DB.
  #
  ####################################################################

  my ($sGetAll);

  $sGetAll = (scalar(@ARGV) == 0) ? 1 : 0;

  ####################################################################
  #
  # Connect to the specified DB.
  #
  ####################################################################

  my ($phDb, $phDbContext, $sLocalError);

  $phDbContext = MldbmNewContext
  (
    {
      'DbFile'     => $$phProperties{'DbFile'},
      'DbFlags'    => O_RDONLY,
      'LockFlags'  => LOCK_SH,
      'LockMode'   => "<",
    }
  );
  if (!MldbmConnect($phDbContext, \$sLocalError))
  {
    print STDERR "$$phProperties{'Program'}: Error='$sLocalError'\n";
    exit(2);
  }
  $phDb = $$phDbContext{'DbHandle'};

  ####################################################################
  #
  # Conditionally spit out the header.
  #
  ####################################################################

  if ($$phProperties{'NoHeader'} == 0)
  {
    if ($sType =~ /^mia$/i)
    {
      print STDOUT "mia|client_id|mia_time|last_checkpoint\n";
    }
  }

  ####################################################################
  #
  # Iterate through the DB hash.
  #
  ####################################################################

  my $sTime = time();

  if ($sGetAll)
  {
    foreach my $sClientId (keys(%$phDb))
    {
      if ($sClientId !~ /^$$phProperties{'CommonRegexes'}{'ClientId'}$/)
      {
        print STDERR "$$phProperties{'Program'}: Warning='Client ($sClientId) does not pass muster. Proceeding anyway.'\n";
      }
      my $phClient = $$phDb{$sClientId};
      if ($sType =~ /^mia$/i)
      {
        if (!exists($$phClient{'Jobs'}{$$phProperties{'Job'}}))
        {
          if (!$$phProperties{'BeQuiet'})
          {
            print STDERR "$$phProperties{'Program'}: Warning='The $$phProperties{'Job'} job has not been assigned to or performed by $sClientId. Use webjob-mldbm-create-job to assign this job to the client.'\n";
          }
          next;
        }
        my $phMiaContext = MiaNewContext
        (
          {
            'Client'     => $sClientId,
            'Checkpoint' => $$phClient{'Jobs'}{$$phProperties{'Job'}}{'Checkpoint'},
            'Period'     => $$phClient{'Jobs'}{$$phProperties{'Job'}}{'Period'},
            'Strict'     => 1,
            'Time'       => $sTime,
          }
        );
        PrintMiaStatus($phProperties, $phMiaContext);
      }
      else
      {
        print STDERR "$$phProperties{'Program'}: Error='Invalid status type ($sType). That should not happen.'\n";
        exit(2);
      }
    }
  }
  else
  {
    foreach my $sClientId (@ARGV)
    {
      if ($sClientId !~ /^$$phProperties{'CommonRegexes'}{'ClientId'}$/)
      {
        print STDERR "$$phProperties{'Program'}: Warning='Client ($sClientId) does not pass muster. Proceeding anyway.'\n";
      }
      if (exists($$phDb{$sClientId}))
      {
        my $phClient = $$phDb{$sClientId};
        if ($sType =~ /^mia$/i)
        {
          if (!exists($$phClient{'Jobs'}{$$phProperties{'Job'}}))
          {
            if (!$$phProperties{'BeQuiet'})
            {
              print STDERR "$$phProperties{'Program'}: Warning='The $$phProperties{'Job'} job has not been assigned to or performed by $sClientId. Use webjob-mldbm-create-job to assign this job to the client.'\n";
            }
            next;
          }
          my $phMiaContext = MiaNewContext
          (
            {
              'Client'     => $sClientId,
              'Checkpoint' => $$phClient{'Jobs'}{$$phProperties{'Job'}}{'Checkpoint'},
              'Period'     => $$phClient{'Jobs'}{$$phProperties{'Job'}}{'Period'},
              'Strict'     => 1,
              'Time'       => $sTime,
            }
          );
          PrintMiaStatus($phProperties, $phMiaContext);
        }
        else
        {
          print STDERR "$$phProperties{'Program'}: Error='Invalid status type ($sType). That should not happen.'\n";
          exit(2);
        }
      }
      else
      {
        print STDERR "$$phProperties{'Program'}: Error='Unknown client ($sClientId).'\n";
      }
    }
  }

  ####################################################################
  #
  # Shutdown and go home.
  #
  ####################################################################

  MldbmDisconnect($phDbContext, \$sLocalError);

  1;


######################################################################
#
# PrintMiaStatus
#
######################################################################

sub PrintMiaStatus
{
  my ($phProperties, $phMiaContext) = @_;

  ####################################################################
  #
  # Compute the MIA status. Treat errors as warnings since we do not
  # want to abort when a particular client is deviant. It's possible
  # the client may not run the specified job.
  #
  ####################################################################

  my ($sLocalError);

  if (!MiaComputeMiaStatus($phMiaContext, \$sLocalError))
  {
    print STDERR "$$phProperties{'Program'}: Error='$sLocalError'\n";
  }

  ####################################################################
  #
  # Convert the MIA delta to Days Hours Minute Seconds (DHMS) format,
  # and spit out an MIA record.
  #
  ####################################################################

  if ($$phMiaContext{'Mia'})
  {
    if ($$phProperties{'ShowAll'} || !$$phProperties{'TimeLimit'} || $$phMiaContext{'Delta'} < $$phProperties{'TimeLimit'})
    {
      print STDOUT join("|", "Y", $$phMiaContext{'Client'}, SecondsToDhms($$phMiaContext{'Delta'}), SecondsToDateTime($$phMiaContext{'Checkpoint'})), "\n";
    }
  }
  else
  {
    if ($$phProperties{'ShowAll'})
    {
      print STDOUT join("|", "N", $$phMiaContext{'Client'}, SecondsToDhms($$phMiaContext{'Delta'}), SecondsToDateTime($$phMiaContext{'Checkpoint'})), "\n";
    }
  }
}


######################################################################
#
# Usage
#
######################################################################

sub Usage
{
  my ($sProgram) = @_;
  print STDERR "\n";
  print STDERR "Usage: $sProgram [-d db] [-l limit] [-o option[,option[,...]]] -j job -t type [client-id ...]\n";
  print STDERR "\n";
  exit(1);
}


=pod

=head1 NAME

webjob-mldbm-get-status - Generate status reports for one or more clients.

=head1 SYNOPSIS

B<webjob-mldbm-get-status> B<[-d db]> B<[-l limit]> B<[-o option[,option[,...]]]> B<-j job> B<-t type> B<[client-id ...]>

=head1 DESCRIPTION

This utility generates status reports for one or more clients.

=head1 OPTIONS

=over 4

=item B<-d db>

Specifies the MLDBM database to query.

=item B<-j job>

Specifies the job to query.

=item B<-l limit>

Specifies the maximum amount of time (seconds) to consider when
reporting MIA clients. If a client is MIA beyond the specified time,
its status will not be reported. This is useful in cases where the
utility is run regularly, but reporting the MIA status for known
offenders would be redundant or unwanted. A value of zero means that
no time limit will be imposed.

=item B<-o option,[option[,...]]>

Specifies the list of options to apply.  Currently, the following
options are supported:

=over 4

=item BeQuiet

Don't print warning messages.

=item NoHeader

Don't print an output header.  Note that this overrides all other
header transformations.

=item ShowAll

Print all entries -- not just the ones that meet the status criteria.

=back

=item B<-t type>

Specifies the type of status report to produce. The following report
types are currently supported: MIA. The value for this option is not
case sensitive.

=back

=head1 AUTHOR

Klayton Monroe

=head1 SEE ALSO

=head1 LICENSE

All documentation and code are distributed under same terms and
conditions as B<WebJob>.

=cut
