#!/usr/bin/perl
#
# NAME
#   localQprep.pm: Perl module defining site-specific qprep callback routines
#
#
# DESCRIPTION
#   This file is part of the Practical Supercomputing Toolkit,
#   http://pstoolkit.org.
#
#   This file contains callback subroutines for use with "qprep(1)",
#   a queue preparation utility that translates from "PSTQ" directives
#   to a form that the local queue system uses.
#
#   To port qprep to your site, modify the contents of each routine in
#   this file.



package TUSC::localQprep;    #Define TUSC/localQprep Perl module

use Exporter;
@ISA = "Exporter";
@EXPORT = qw(qprepInitCallback,
             qprepPreTranslationCallback,
             qprepFinishScriptCallback,
             qprepFinishPreambleCallback
             );

$VERSION = "1.00";

use English;     # Provide symbols for improved Perl readability.
use strict;      # Enhance Perl strictness, for aid in development.
use FileHandle ; # Use sysopen to open the script file


####use warnings;




=pod

  NAME
    qprepInitCallback: initialization callback for qprep


  ARGUMENTS
   \%state_table (in/out): reference to hash table of qprep options
     See qprepStateInitializeParserControls.

   \%directives_table (in/out): table of valid directives
     See qprepValidDirectiveTableInitialize.

   \%translation_table (in/out): hash table translation rules.
     See qprepTranslationTableInitialize.

   $input_stream (in): file handle for input script

   $output_stream (in): file handle for output script

   \@cmd_line_directives_array (in): array of directive strings
     See qprepCommandLineDirectivesParse.


  DESCRIPTION
    qprep calls this routine after performing generic initialization
    of the hash tables and reading and evaluating the command-line,
    and after opening the input script for reading, but before reading
    or evaluating input from the input script.  This routine specifies
    many of the parameter values specific to the site, by setting values
    in the hash tables.

    The primary utility of this routine is in establishing translation
    rules via the translation table and in special entries in the
    state table.

    State table entries related to translation:

      target_comment_prefix: string denoting start of comment in the
        language used for scripts that go to the local queue system.
        That language is usually  ashell, like sh or csh, so this value is
        "#" (the hash character) by default, and this routine probably
        does not need to change its value.

      target_comment_suffix: string denoting end of comment in the
        language used for scripts that go to the local queue system.
        That language is usually  ashell, like sh or csh, so this value
        is "" (the empty string) by default, and this routine probably
        does not need to change its value.

      target_directive_prefix: string that precedes directives
        in scripts going to local queue system.
        For example, in PBS, this is usually "#PBS -",
        and in LSF this is "#BSUB -".

      target_key_value_separator: string that separates directive keys
        and values scripts going to in local queue system.

      target_submit_command: command to use to submit script.
        For example, in PBS this is "qsub", and in LSF this is "bsub".

    This routine can set default values for any qprep directives.
    See qprep(1) for details about qprep directives.

    This routine is obliged either not to read from the input stream,
    or to return the input stream back to the original location (i.e.,
    the beginning of the file).  Theoretically, the input stream could
    be non-seekable, in which case this routine is strongly obliged not
    to read from the input stream.  Remember: Just because you can does
    not mean you should.


  SEE ALSO
    qprep(1),

    In "qprep.pm": (in "PST/TUSC")
      qprepMain(3), qprepStateInitialize(3),
      qprepMessageTableInitialize(3),
      qprepValidDirectiveTableInitialize(3),
      qprepTranslationTableInitialize(3), qprepCommandLineParse(3),
      qprepSetOutputScriptFileName(3), qprepInputScriptFileParse(3),
      qprepCommandLineDirectivesParse(3),

    In this file:
       qprepFinishPreambleCallback(3), qprepFinishScriptCallback(3),



=cut
sub qprepInitCallback
{
  # Unpack subroutine arguments from Perl ARG array (a.k.a. @_).
  my($state_table_ref,
     $directives_table_ref,
     $translation_table_ref,
     $input_stream,
     $output_stream,
     $cmd_line_directives_array_ref)
     = @ARG;

  if ( $state_table_ref->{operation_verbosity} >= 2 )
  {
    print STDERR  "qprepInitCallback: ",
          "$state_table_ref $directives_table_ref ",
          "$translation_table_ref $input_stream ",
          "$output_stream $cmd_line_directives_array_ref\n";
  }

  #####
  ##### Assign state table entries for default values
  #####
  ###################################################
  ###################################################

  $state_table_ref->{hi}                         = "n" ;

  #### Properties associated with writing output:

  $state_table_ref->{target_comment_prefix}      = "#"        ;
  $state_table_ref->{target_comment_suffix}      = ""         ;
  $state_table_ref->{target_directive_prefix}    = "#@ "      ;
  $state_table_ref->{target_key_value_separator} = " = "      ;
  $state_table_ref->{target_submit_command}      = "llsubmit" ;

  # Limit directives.
  #
  # LoadLeveler only has "number of cpus",
  # so qprepPreTranslationCallback handles these.
  $state_table_ref->{nodes}             = "unset";
  $state_table_ref->{cpus_per_node}     = "unset";

  # PBS wants all job dependencies in one directive.
  # qprepPreTranslationCallback handles them.
  $state_table_ref->{sync_count}        = "unset" ;
  $state_table_ref->{sync_with}         = "unset" ;
  $state_table_ref->{after}             = "unset" ;
  $state_table_ref->{after_ok}          = "unset" ;
  $state_table_ref->{after_not_ok}      = "unset" ;
  $state_table_ref->{after_any}         = "unset" ;
  $state_table_ref->{depends_on}        = "unset" ;
  $state_table_ref->{before}            = "unset" ;
  $state_table_ref->{before_ok}         = "unset" ;
  $state_table_ref->{before_not_ok}     = "unset" ;
  $state_table_ref->{before_any}        = "unset" ;

  #####
  ##### Assign translation table entries
  #####
  ###################################################
  ###################################################

  # Job environment directives
  $translation_table_ref->{account}       = [('KEY',"s/account/account_no/")] ;

  # Reporting directives
  $translation_table_ref->{stderr}        = [('KEY', "s/stderr/error/")] ;
  $translation_table_ref->{stdout}        = [('KEY', "s/stdout/output/")] ;

  # Mail options:
  # [I need to find out about other mail options for LoadLeveler.
  #  It's possible that we need a PostTranslationCallback
  #  to remove any non-supported values.  --MJG]
  $translation_table_ref->{mail}                =
    [
      ('KEY',   "s/mail/notification/"),
      ('VALUE', "s/[abrt]//g"),
      ('VALUE', "s/e/complete/g"),
      ('VALUE', "s/,//g")
    ] ;

  $translation_table_ref->{mail_to}    = [('KEY', "s/mail_to/notify_user/")] ;

  # Job control directives

  # [LoadLeveler apparently wants this in seconds, so we might have to
  #  translate the time inside the pre-translation callback.  --MJG]

  $translation_table_ref->{wall_time}           =
    [ ('KEY', 's/wall_time/wall_clock_time/') ] ;

  # Limit directives
  $translation_table_ref->{nodes}         = [('KEY', "s/nodes/node/")] ;
  $translation_table_ref->{cpus_per_node} = [('KEY', "s/cpus_per_node/tasks_per_node/")] ;

  # Job dependence directives

  # Catch-all directive: default translation is okay.

  # Special state variable.
  # before_first_directive is my temporary hack to handle
  # certain LoadLeveler directives.  See qprepPreTranslationCallback. --MJG
  $state_table_ref->{before_first_directive}     = "y";

  #####
  ##### Augment table of valid directives
  #####
  ###################################################
  ###################################################

  return ;
}




=pod

  NAME
    qprepPreTranslationCallback: directive pre-translation callback for qprep


  ARGUMENTS
   \%state_table (in/out): reference to hash table of qprep options
     See qprepStateInitializeParserControls.

   \%directives_table (in): table of valid directives
     See qprepValidDirectiveTableInitialize.

   $directive_key_ref (in/out): reference to directive key scalar

   $directive_value_ref (in/out): reference to directive key scalar

   $output_stream (in): file handle for output script


  DESCRIPTION
    qprep calls this routine after reading an input directive,
    but before applying the translation table.

    The primary utility of this routine is to record or alter state
    information and write directives in the local queue system language
    which would be too complicated or impossible to handle with the
    translation table.

    For example, some systems distinguish between "nodes" and "cpus",
    and to reflect this, qprep has directives to set each of these.
    Many systems, however, do not make this distinction, so you simply
    want to set the number of CPUs for the job to use.  A simple
    translation will not, in general, work, since the idea behind qprep
    is to allow users to write one script that works on all systems,
    hence the user often must specify "nodes" and "cpus_per_node",
    even on systems where those do not directly apply.  The solution is
    to write code in this routine to handle that situation:

    () qprepInitCallback would initialize state_table entries for "nodes"
       and "cpus_per_node" to special nonsense values to indicate to
       this routine that those values have not yet been set.

    () This routine would check to see whether directive_key_ref is either
       "nodes" or "cpus_per_node".

       [] If not, this routine ignores the input and returns.

       [] If so, this routine checks to see whether both values are unset.

          {} If not, this routine returns.

          {} If so then this routine alters the contents of
             directive_key_ref to the directive key that the local
             queue system uses to indicate the number of CPUs, sets
             directive_value_ref to that value, stores the value in
             the state table (which should be the product of node and
             cpus_per_node), and returns.

    () The qprepFinishPreambleCallback must also check to see whether
       this routine performed that last step.  If not then
       qprepFinishPreambleCallback must write the appropriate directive.

    This returned directive information will pass through table-based
    translation, and therefore the directives_table and translation_table
    must have an entry for the target directive key, and the associate
    directive table acceptance pattern must allow this value, or the qprep
    directive checker will reject the directive, report an error, and die.

    This routine can also handle other similar situations, such as
    in PBS, where multiple resources are set with a single directive.
    The solution there is similar to the one described above.


  SEE ALSO
    In this file:
       qprepInitCallback(), qprepFinishPreambleCallback(),
       qprepFinishScriptCallback().



=cut
sub qprepPreTranslationCallback
{
  # Unpack subroutine arguments from Perl ARG array (a.k.a. @_).
  my($state_table_ref,
     $directives_table_ref,
     $directive_key_ref,
     $directive_value_ref,
     $output_stream) = @ARG;

  if ( $state_table_ref->{operation_verbosity} >= 2 )
  {
    print STDERR  "qprepPreTranslationCallback: ",
          "$state_table_ref $directives_table_ref " ,
          "$directive_key_ref = $$directive_key_ref ",
          "$directive_value_ref = $$directive_value_ref " ,
          "$output_stream\n";
  }

  if($$directive_key_ref eq "wall_time")
  {
     # Convert hours:minutes to seconds.
     my($hours);
     my($minutes);

     # Strip off minutes to get hours.
     ( $hours   = $$directive_value_ref ) =~ s/:[0-9]*$// ;

     # Strip off hours to get minutes.
     ( $minutes = $$directive_value_ref ) =~ s/^[0-9]*:// ;

     # Compute seconds
     my($seconds) = $hours * 3600 + $minutes * 60;

     $$directive_value_ref = "$seconds";
  }

  if ( $state_table_ref->{before_first_directive} eq "y" )
  {
    $state_table_ref->{before_first_directive} = "n" ;

    # Write some directives unconditionally.
    # [I am not clear on handling some of these features of LoadLeveler.
    #  The proposed conditional directives might be the best way to handle
    #  them.  --MJG]
    print $output_stream
          "$state_table_ref->{target_directive_prefix}",
          "job_type = parallel\n" ;

    print $output_stream
          "$state_table_ref->{target_directive_prefix}",
          "network.MPI = css0,not_shared,US\n" ;
  }

  # Return status code meaning "no errors detected."
  return 0;
}




sub qprepPostTranslationCallback
{
  # Unpack subroutine arguments from Perl ARG array (a.k.a. @_).
  my($state_table_ref,
     $directives_table_ref,
     $directive_key_ref,
     $directive_value_ref,
     $output_stream) = @ARG;

  if ( $state_table_ref->{operation_verbosity} >= 2 )
  {
    print STDERR  "qprepPostTranslationCallback: ",
          "$state_table_ref $directives_table_ref " ,
          "$directive_key_ref = $$directive_key_ref ",
          "$directive_value_ref = $$directive_value_ref " ,
          "$output_stream\n";
  }

  if($$directive_key_ref eq "notification")
  {
    if($$directive_value_ref eq "")
    {
      # Input specified a 'notification' directive (in the form of a
      # 'mail' directive) with a value that target job queue system does
      # not support; clean up empty notification directive.
      $$directive_key_ref = "";
    }
  }

  # Return status code meaning "no errors detected."
  return 0;
}




=pod

  NAME
    qprepFinishPreambleCallback: post-preamble callback for qprep


  ARGUMENTS
   \%state_table (in/out): reference to hash table of qprep options
     See qprepStateInitializeParserControls.

   \%directives_table (in): reference to hash table of valid directives
     See qprepValidDirectiveTableInitialize.

   $input_stream (in): file handle for input script

   $output_stream (in): file handle for output script


  DESCRIPTION
    qprep calls this routine after reading the entire preamble,
    but before reading the rest of the input script.

    The primary utility of this routine is to write pending write
    directives in the local queue system language.  See the description of
    qprepPreTranslationCallback for an example of when this is necessary.



  SEE ALSO
    In this file:
       qprepInitCallback(),
       qprepFinishScriptCallback(). qprepPreTranslationCallback



=cut
sub qprepFinishPreambleCallback
{
  # Unpack subroutine arguments from Perl ARG array (a.k.a. @_).
  my($state_table_ref,
     $directives_table_ref,
     $input_stream,
     $output_stream) = @ARG;

  if ( $state_table_ref->{operation_verbosity} >= 2 )
  {
    print STDERR  "qprepFinishPreambleCallback: ",
          "$state_table_ref $directives_table_ref $input_stream ",
          "$output_stream\n";
  }

  print $output_stream
        "$state_table_ref->{target_directive_prefix}",
        "queue\n" ;
}




=pod

  NAME
    qprepFinishScriptCallback: post-input script callback for qprep


  ARGUMENTS
   \%state_table (in/out): reference to hash table of qprep options
     See qprepStateInitializeParserControls.

   \%directives_table (in): reference to hash table of valid directives
     See qprepValidDirectiveTableInitialize.

   $input_stream (in): file handle for input script

   $output_stream (in): file handle for output script


  DESCRIPTION
    qprep calls this routine after reading the entire input script,
    but before closing the input and output streams.

    This routine should not be necessary (or even useful) for translating
    queue directives, but could be useful for chaining other commands
    or further altering the output script, such as to effectively unify
    a job execution command (e.g. mpprun).


  SEE ALSO
    In this file:
       qprepInitCallback(),
       qprepFinishScriptCallback(). qprepPreTranslationCallback



=cut
sub qprepFinishScriptCallback
{
  # Unpack subroutine arguments from Perl ARG array (a.k.a. @_).
  my($state_table_ref,
     $directives_table_ref,
     $input_stream,
     $output_stream) = @ARG;

  if ( $state_table_ref->{operation_verbosity} >= 2 )
  {
    print STDERR  "qprepFinishScriptCallback: ",
          "$state_table_ref $directives_table_ref $input_stream ",
          "$output_stream\n";
  }

  print $output_stream
    "$state_table_ref->{target_comment_prefix}",
    " qprepFinishScriptCallback: bye.",
    "$state_table_ref->{target_comment_suffix}\n";
}




__END__
