#!/usr/bin/perl ############################################################################# # JOBFILTER # Written by Scottmo # :set tabstop=3 # to view indentation at proper setting ############################################################################## ############################################################################## # MAIN ############################################################################## &Initialize; &CheckDisabled; &ReadJob; &ParseCommand; &VerifyConfig; &CheckPHLimits; &SubmitJob; &LogJob; exit 0; ############################################################################## # INITIALIZATION SUBROUTINE ############################################################################## sub Initialize { #$debug = 1; if ($ARGV[0] eq "-d") { $debug = 1 } if ($ENV{MP_INFOLEVEL}>0) { $debug=1 } $user=(getpwuid($<))[0]; $group=(getgrgid($())[0]; $INTERACTIVE_CLASS="interactive"; $TYPE = "SERIAL"; $LogFile = ">>/tmp/submit.history"; $ExceptionFile = "/u/maui/exceptions.cw1"; $DisabledFile = "/u/maui/disabled.cw1"; } ############################################################################## # READ JOB COMMAND FILE FROM STDIN ############################################################################## sub ReadJob { if ($debug) { print STDERR "***Reading Job Command File***\n"; } @CMD=; # Capture STDIN } ############################################################################## # PROCESS THE COMMAND FILE FROM STDIN ############################################################################## sub ParseCommand { if ($debug) { print STDERR "***Parsing Job Command File***\n"; } local(@LCMD)=@CMD; @HEAD=(); %KEYWORD=(); while ($_=shift(@LCMD)) { # Parse Local Copy of Job Command File if (/^\s*#\s*@\s*[\w\.]+\s*=*\s*/) { # Look for #@KEYWORD= format $key=$&; $expression=$'; # Separate keyword from statement $key =~ s/[\#@=\s]+//g; # Strip #,@,= and whitespace $key =~ y/a-z/A-Z/; # Uppercase the key $expression =~ s/\s+$//; # Strip trailing whitespace from expr if ($key =~ /QUEUE/) { @TAIL=@LCMD; # Copy remainder of Command File into @TAIL last; # Break out of while if reach queue statement } chomp($KEYWORD{$key}=$expression); # Add Statement to KEYWORD array if ($debug) { print STDERR "#@ $key = $KEYWORD{$key}\n"; } } else { push(@HEAD,$_); } # Include non-keywords in @HEAD } } ############################################################################## # VERIFY CONFIGURATION ############################################################################## sub VerifyConfig { if ($debug) {print STDERR "***Verifying Configuration***\n";} if ($KEYWORD{"CLASS"} =~ /interactive/i) { # If an interactive job # Override ACCOUNT_NO Keyword with MP_ACCOUNT Environment Variable if (defined $ENV{MP_ACCOUNT}) { $KEYWORD{"ACCOUNT_NO"} = $ENV{MP_ACCOUNT}; } # Override WALL_CLOCK_LIMIT Keyword with MP_WCLIMIT Env Variable if (defined $ENV{MP_WCLIMIT}) { $KEYWORD{"WALL_CLOCK_LIMIT"} = $ENV{MP_WCLIMIT}; } # Override COMMENT Keyword with QUEUEJOB Env Variable if (!defined $ENV{MP_QUEUEJOB} || $ENV{MP_QUEUEJOB} eq "FALSE") { $KEYWORD{"COMMENT"} .= ";QUEUEJOB=FALSE" } } else { # If a batch job } # Abort if no account_no is specified if (!defined $KEYWORD{"ACCOUNT_NO"}) { die "You MUST specify a valid ACCOUNT.\nIf a batch job specify #@ ACCOUNT_NO= in the Job Comand File.\nIf an interactive job set the environment variable MP_ACCOUNT=\n."} # Abort if no class is specified if (!defined $KEYWORD{"CLASS"}) { die "You MUST specify a valid CLASS in the Job Command File\n"} # Verify user is a member of account #chop($membership=`/loadl/qbank/bin/qmember $user $JobConfig{account}`); #unless ($membership eq "1") # { die "$user is not a valid member of account $JobConfig{account}\n" } # If WALL_CLOCK_LIMIT not defined, set it to default class limit if (!defined $KEYWORD{"WALL_CLOCK_LIMIT"}) { $WALLCLOCKLIMIT=`/usr/lpp/LoadL/full/bin/llclass -l $KEYWORD{"CLASS"}|grep Wall_clock_limit`; ($space,$tag,$_,$HARD_WALLCLOCKLIMIT)=split(/[\s,]+/,$WALLCLOCKLIMIT); if ($debug) { print STDERR "No WALL_CLOCK_LIMIT specified -- defaulting to class limit of $_\nIf a batch job specify #@ WALL_CLOCK_LIMIT= in the Job Command File.\nIf an interactive job set the environment variable MP_WCLIMIT=.\n must be of the form \"[DAYS+[[HOURS:]MINS:]]SECS\"\n"; } if (($days,$hrs,$mins,$secs) = /^(\d+\+)?(\d{1,2}:)?(\d{1,2}:)?(\d+)/) { $KEYWORD{"WALL_CLOCK_LIMIT"} = $days*86400+$hrs*3600*$mins*60+$secs*1 } else { die "WALL_CLOCK_LIMIT must be of the form \"[DAYS+[[HOURS:]MINS:]]SECS\"\n";} } # Set MP_WCLIMIT environment variable (from #@WALL_CLOCK_LIMIT) so prolog can parse it $_=$KEYWORD{"WALL_CLOCK_LIMIT"}; ; if (($days,$hrs,$mins,$secs) = /^(\d+\+)?(\d{1,2}:)?(\d{1,2}:)?(\d+)/) { $ENV{"MP_WCLIMIT"} = $days*86400+$hrs*3600*$mins*60+$secs*1 } else { die "MP_WCLIMIT must be of the form \"[DAYS+[[HOURS:]MINS:]]SECS\"\n";} # Set #@COMMENT = QOS= if #@QOS is specified in Job Command File if (defined $KEYWORD{QOS}) { $KEYWORD{"COMMENT"} .= ";QOS=$KEYWORD{QOS}"; delete $KEYWORD{QOS}; } # If MP_QOS environment variable defined, Override #@COMMENT = QOS=$MP_QOS if (defined $ENV{MP_QOS}) { $KEYWORD{"COMMENT"} .= ";QOS=$ENV{MP_QOS}"; } } ############################################################################## # SUBMIT JOB ############################################################################## sub SubmitJob { foreach (sort keys %KEYWORD) { push(@BODY,"#@ $_ = $KEYWORD{$_}\n"); } push(@BODY,"#@ QUEUE\n"); print STDOUT (@HEAD,@BODY,@TAIL); #print STDERR (@HEAD,@BODY,@TAIL); if ($debug) {print STDERR "***Job Submitted***\n";} `/loadl/maui/bin/wakeup`; } ############################################################################## # LOG JOB ############################################################################## sub LogJob { if ($debug) {print STDERR "***Logging Job***\n";} #open(LOGFILE,$LogFile.".".`date +"%m%d%y"`); open(LOGFILE,$LogFile); chop($date=`date`); printf LOGFILE "NEW JOB SUBMITTED by %s at %s\n",$user,$date; print LOGFILE (@HEAD,@BODY,@TAIL); #foreach (@HEAD,@BODY,@TAIL) { print LOGFILE "$_" } print LOGFILE "------------------------------------------------------------------------------\n"; } ############################################################################## # CHECK DISABLED USERS ############################################################################## sub CheckDisabled { open(DISABLEDFILE,$DisabledFile); while () { next if (s/^#//); ($entity,$name)=split(/\s+/,$_); if (($entity eq "user" && $name eq $user) || ($entity eq "group" && $name eq $group)) { die("** Job Submission has been DISABLED for this Account **\n") ; } } } ############################################################################## # CHECK NUMBER OF PROCS VS CLOCK TIME ############################################################################## # The format of the exceptions file is: # username min_procs max_procs max_hours # # The number of nodes in this file are inclusive numbers # # No white space is allowed at the beginning of the line. The # remaining three fields are numeric, white space separated. # $KEYWORD{NODE} contains the requested # of nodes # $KEYWORD{WALL_CLOCK_LIMIT} contains the requested number of hours # # The parse of the exceptions file exits with 0 or 1 on the first match for # the number of nodes that has been requested. # # If there is no match in the exceptions file, then use the default values. # Defaults are: # NODE RANGE MAX # HOURS # ---------- ----------- # 1-64 144 ############################################################################## sub CheckPHLimits { my @EXCEPTIONS; my $exception; my $minnodes,$maxnodes,$maxhours; # Calculate Required Hours $_ = $KEYWORD{WALL_CLOCK_LIMIT}; ($grp,$hrs,$min1,$min2,$sec) = /^((\d+:)(\d{1,2}:)|(\d{1,2}:))?(\d+)/; $reqHours = sprintf("%.2f",($hrs*3600+($min1+$min2)*60+$sec*1)/3600); # Calculate Required Processors if (defined $KEYWORD{MIN_PROCESSORS}) { $reqProcs = $KEYWORD{MIN_PROCESSORS}; } elsif (defined $KEYWORD{MAX_PROCESSORS}) { $reqProcs = 1; } elsif (defined $KEYWORD{NODE}) { $_=$KEYWORD{NODE}; ($min,$comma,$max)=/^(\d+)*(,(\d+))*$/; if ($min) { $reqProcs = $min; } elsif ($max) { $reqProcs = 1; } #$reqProcs = $KEYWORD{NODE}; if (defined $KEYWORD{TASKS_PER_NODE}) { $reqProcs*=$KEYWORD{TASKS_PER_NODE}; } elsif (defined $KEYWORD{TOTAL_TASKS}) { $reqProcs=$KEYWORD{TOTAL_TASKS}; } } elsif (defined $ENV{MP_PROCS}) { $reqProcs = $ENV{MP_PROCS}; } else { die "The NODE Keyword must be specified in the Job Command File\n"; } if ($debug) {print STDERR "NODEHOURS REQUESTED: $user requested $reqProcs processors for $reqHours hours\n";} # # First process the exceptions # open(EXCEPTIONFILE,$ExceptionFile); @EXCEPTIONS = grep(/^$user/,); close EXCEPTIONFILE; foreach $exception (@EXCEPTIONS) { chomp $exception; $exception =~ /\w+\s+(\d+)\s+(\d+)\s+(\d+)/; $minnodes = $1; $maxnodes = $2; $maxhours = $3; if ($debug) { print STDERR "EXCEPTION: $user can run on $minnodes >= procs <= $maxnodes for up to $maxhours hours\n"; } # # See if the exception allows the job to run # if ($reqProcs >= $minnodes && $reqProcs <= $maxnodes && $reqHours <= $maxhours) { if ($debug) { print STDERR "EXCEPTED INCLUSION: $user can run on $minnodes >= processors <= $maxnodes for up to $maxhours hours\n"; } return 0; } # # See if the exception prevents the job from running # if ($reqProcs >= $minnodes && $reqProcs <= $maxnodes && $reqHours > $maxhours) { if ($debug) { print STDERR "EXCEPTED EXCLUSION: $user can run on $minnodes >= processors <= $maxnodes for up to $maxhours hours\n"; } &excludeNdie; } } # # Check for the default time limits # if ($reqProcs < 8 || $reqProcs > 256) { if ($debug) {print STDERR "DEFAULT EXCLUSION:REQUESTED - $reqProcs processors for $reqHours hours:$user:<8 && > 256\n";} &excludeNdie; } if ($reqProcs >= 8 && $reqProcs <= 64 && $reqHours <= 72) { if ($debug) {print STDERR "DEFAULT INCLUSION: REQUESTED - $reqProcs processors for $reqHours hours:$user:8:64:72\n";} return 0; } if ($reqProcs >= 65 && $reqProcs <= 128 && $reqHours <= 48) { if ($debug) {print STDERR "DEFAULT INCLUSION:REQUESTED - $reqProcs processors for $reqHours hours:$user:65:128:48\n";} return 0; } if ($reqProcs >= 129 && $reqProcs <= 256 && $reqHours <= 24) { if ($debug) {print STDERR "DEFAULT INCLUSION:REQUESTED - $reqProcs processors for $reqHours hours:$user:129:256:24\n";} return 0; } &excludeNdie; } ############################################################################## # PRINT STANDARD INFORMATION AND EXIT ############################################################################## # # Print out the allowable time limits for a job # with what was requested and what the user exceptions are. # Then die to prevent the job from running. # ############################################################################## sub excludeNdie { my $message; my $hours; $message = "Your job is outside the allowed node-hour limits.\n"; $message = "You requested $reqProcs processors for $reqHours hours.\n"; $message .= "The default limits for nwmpp1 are:\n"; $message .= "NODE RANGE MAX HOURS\n"; $message .= "---------- ---------\n"; $message .= " <8 0 \n"; $message .= " 8-64 72 \n"; $message .= " 65-128 48 \n"; $message .= " 129-256 24 \n"; $message .= " >256 0 \n"; if ($#EXCEPTIONS >= 0) { $message .= "Your personal limits that override the defaults are:\n"; foreach $exception (@EXCEPTIONS) { chomp $exception; $exception =~ /\w+\s+(\d+)\s+(\d+)\s+(\d+)/; $message .= "From $1 nodes to $2 nodes for a maximum of $3 hours\n"; } } die $message; } ############################################################################## # MISCELLANEOUS SUBROUTINES ############################################################################## sub bynumber { $a <=> $b; } sub isnum { $_="@_"; ($D1,$d,$D2)=/([a-zA-Z_]*)(\d*)([a-zA-Z_]*)/; if ($D1 || $D2) { return 0 } else { return $d } }