#!/usr/bin/perl
#########################################################################
##
## © University of Southampton IT Innovation Centre, 2004
##
## Copyright in this library belongs to the IT Innovation Centre of
## 2 Venture Road, Chilworth Science Park, Southampton SO16 7NP, UK.
##
## This software may not be used, sold, licensed, transferred, copied
## or reproduced in whole or in part in any manner or form or in or
## on any media by any person other than in accordance with the terms
## of the Licence Agreement supplied with the software, or otherwise
## without the prior written consent of the copyright owners.
##
## This software is distributed WITHOUT ANY WARRANTY, without even the
## implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
## PURPOSE, except where stated in the Licence Agreement supplied with
## the software.
##
##      Created By :            Panos Melas
##      Created Date :          2005/03/18
##      Created for Project:    GRIA
##
#########################################################################
##
##      Dependencies : none
##
#########################################################################
##
##      Last commit info:       $Author: cruisecontrol $
##                              $Date: 2007-02-07 16:57:58 +0000 (Wed, 07 Feb 2007) $
##                              $Revision: 6548 $
##
#########################################################################


###############################################################
# Start job script API
#
# startJob 
#           -d <session directory>
#           -e <path to application wrapper script>
#          [-r <RM instructions>]...   
#          [-h ]
#          [-v ] verbose
#          [-- [application arguments]...]
#

open(STDOUT, '>>log') or die "Failed to open log: $!, stopped ";
open(STDERR, '>&STDOUT') or die "Failed to duplicate stdout to stderr: $!, stopped";

# comment out, see windows support.
#use strict;
use Cwd;

printHeader();

###############
#  SECTION A  #
#################################################
#
# Initialise Resource Manager global vars
#

my $RM_PATH="/usr/local/bin";
my $RM_QUEUE=$RM_PATH . "/qstat";
my $RM_SUBMIT=$RM_PATH . "/qsub";
my $RM_SERVER="doryssa";


###################################################
# 
# Initialise timestamp variables used by the API
#

# time line
#              (1)           (2)     (3)        (4) (5)
#|--------------^-------------^-------^----------^---^----->
#     |___________|  queue    |       |__________|   |
#       startJob              |        application   |
#                             |______________________|
#                                  app-wrapper


# (1)
my $JOB_SUBMIT_TS=".job_submitted";
# (2)
my $APP_WRAPPER_STARTED_TS=".app_wrapper_started";
# (3)
my $APP_STARTED_TS=".app_started";
# (4)
my $APP_ENDED_TS=".app_ended";
# (5)
my $APP_WRAPPER_ENDED_TS=".app_wrapper_ended";

my $APP_EXIT_STATUS=".app_exit_status";

my $EXIT_CODE=".exit_code";

my $JOB_PID=".jobPID";


####################################################
#
# Default values for some command line args
#

my $SESSION_NAME="TEST";
my $SESSION_DIR=".";
my $WORK_DIR="work";
my $EXE_WRAPPER;
my $STAGER_DIR="staged";



###############
#  SECTION B  #
####################################################
#
# Verbose mode flags

my $debug=0;


####################################################
#
# Parse command line arguments
# 

# group arguments in arrays
my @arArgument;
my @arInput;
my @arOutput;
my @arResource;

# auxiliary flags 
my $flag="";
my $dflag=0;
my $pflag=0;
my $sflag=0;
my $arg;

foreach $arg ( @ARGV ) {
	if(($arg eq "-h") && ($flag eq "")) {
		usage();
	} elsif(($arg eq "-v") && ($flag eq "")) {
		$debug = 1;
	} elsif($arg eq "-d") {
		if (  ($flag eq "a" ) && ($dflag == 1 )) {
			push @arArgument, $arg;
		} else {
			$flag = "d";
		}
	} elsif($arg eq "-e") { 
		if ( ($flag eq "a" ) && ($pflag == 1 ) ) {
			push @arArgument, $arg;
		} else {
			$flag = "p";
		}
	} elsif($arg eq "-r") { 
		if ( ($flag eq "a" ) && ($dflag == 1 ) ) {
			push @arArgument, $arg;
		} else {
			$flag = "r";
		}
	} elsif($arg eq "--") { 
		if ( ($flag eq "a" ) && ($dflag == 1 ) ) {
			push @arArgument, $arg;
		} else {
			$flag = "a";
		}
	} else {
		if ($flag eq "r" ) {
			push @arResource, $arg;
		} elsif (($flag eq "d") && ($dflag == 0) ) {
			$SESSION_DIR = $arg;
			$dflag = 1;
		} elsif (($flag eq "p") && ($pflag == 0) ) {
			$EXE_WRAPPER = $arg;
			$pflag = 1;
		} else {
			push @arArgument, $arg;
		}
	}
}

# DEBUG: show found arguments
print ("Script arguments found:\n" .
"Session directory            : $SESSION_DIR\n" .
"Wrapper path                 : $EXE_WRAPPER\n" .
"Application arguments     [".scalar(@arArgument)."]: @arArgument\n" .
"Resource manager arguments[".scalar(@arResource)."]: @arResource\n\n" ) if ($debug == 1);

if ($^O != /win/i) {
	-x "$EXE_WRAPPER" || die("Wrapper script is not executable. Use 'chmod a+x $EXE_WRAPPER', stopped ");
}

-d $WORK_DIR or die "Work dir '$WORK_DIR' not found! ($!), stopped ";

#########################################################
# compose the argument string to invoke the application wrapper
# 
my @auxArgs;


foreach my $var (@arArgument) {
	push @auxArgs, "-a=$var";
}

print ("Composed argument <@arArgument>\n") if ($debug==1);

# store arguments in .arguments file
my $argumentFile = ".arguments";
open(FILE, ">$argumentFile") || die ("Error cannot store arguments: $!");
foreach $var (@arArgument) {
	print FILE "$var\n";
}
close FILE;

#########################################################
# process any resource arguments
#
my $raString = processResourceArguments();


###############
#  SECTION C  #
######################################################
# generate JDF file, this is shell script file
#
my $JDF="jdf.sh";
open(FILE, ">$JDF") || die ("Error cannot open $JDF file: $!, stopped ");

print FILE "#!/bin/sh -f
# Job Description File for job: $SESSION_DIR
## PBS directives first
#PBS -N J${SESSION_NAME}
#PBS -o pbs_job.log
#PBS -j oe
##PBS -q dque
${raString}

##########################################
#   Print some useful job information.   

echo ------------------------------------------------------
echo -n 'Job is running on node '; cat \$PBS_NODEFILE
echo ------------------------------------------------------
echo PBS: qsub is running on \$PBS_O_HOST
echo PBS: originating queue is \$PBS_O_QUEUE
echo PBS: executing queue is \$PBS_QUEUE
echo PBS: working directory is \$PBS_O_WORKDIR
echo PBS: execution mode is \$PBS_ENVIRONMENT
echo PBS: job identifier is \$PBS_JOBID
echo PBS: job name is \$PBS_JOBNAME
echo PBS: node file is \$PBS_NODEFILE
echo PBS: current home directory is \$PBS_O_HOME
echo PBS: PATH = \$PBS_O_PATH
echo ------------------------------------------------------

cd \$PBS_O_WORKDIR/${WORK_DIR}
exec > ../log 2>&1

WRAPPER=${EXE_WRAPPER}
# execute program (wrapper script)
touch ../$APP_WRAPPER_STARTED_TS
#${EXE_WRAPPER} @arguments
xargs -0 \$WRAPPER < ../.arguments
echo \$? > ../.app_wrapper_exit_code
touch ../$APP_WRAPPER_ENDED_TS

";
close FILE;


###############
#  SECTION D  #
##########################################################
# submit the JDF to PBS
#
##################################
# Ready to submit the job now ...
print ("Ready to submit the job locally ...\n") if $debug;
# touch submit file
open(FILE1, ">$SESSION_DIR/$JOB_SUBMIT_TS") || 
    die ("Error cannot create  $JOB_SUBMIT_TS file: \$!, stopped ");

# compose submit command to the default queue
my $command_line="$RM_SUBMIT -q \@$RM_SERVER $JDF";

# execute the submit command and store submission job ID
my $sub = extInvoke("system", "$command_line > $JOB_PID");

# exit if submission failed
unless ($sub == 0 ) {
    print "job submit failed: $sub\n";
    exit($sub);
}


###########################################################
# check submitted PID
# 
die ("Error: submit failed to generate job PID file $!, stopped ") unless ( -f $JOB_PID ); 

###########################################################
# Exit submission successfully
# 
exit;



############## perl subroutines ###################



###############
#  SECTION E  #
###################################################
# use this subroutine to convert RM directives into
# PBS directives that will be inserted in the JDF
# file
 
sub processResourceArguments () {
	my @constraints = "";
	
	my %rm_hash = ();
	my @pair;
	
	my $JRDF="user-constraints.xml";
	if ( -e $JRDF ) {
		print "JRDF found: $JRDF\n";
		
		# check for perl module XML::Simple
		eval { require XML::Simple; };
		if ($@) {
			die "ERROR: Perl module XML::Simple is not installed! (e.g. install package perl-XML-Simple) $!, stopped ";
		} else {
			print ("DEBUG: XML::Simple module detected ok\n") if $debug;
		}	
		require XML::Simple;
		my $xml = new XML::Simple;
		my $doc = $xml->XMLin($JRDF);
		LINE: foreach my $key (keys (%{$doc})) {
			next LINE if $key =~ /^xmlns/; #skip namespace attribute	
			print "JRDF constraint: $key <$doc->{$key}>\n" if $debug ;
			$rm_hash{ $key } =  $doc->{$key};
		}
	}
	
	# merge with constraints from the job service 
	foreach my $val (@arResource) {
		die "Error : cannot parse RM argument: $val, stopped " if ($val !~ /[^\s]+\s*=\s*[^\s]+/);
		@pair = split('=', $val);
		if (exists $rm_hash{ $pair[0] }) {
			if ($pair[0] eq "WallClockTime") {
				# get minimum
				if ($rm_hash{ $pair[0] } > $pair[1]) { 
					$rm_hash{ $pair[0] } = $pair[1];
				}
			}
			if ($pair[0] eq "PhysicalMemory") {
				# get maximum
				if ($rm_hash{ $pair[0] } < $pair[1]) { 
					$rm_hash{ $pair[0] } = $pair[1];
				}
			}
			if ($pair[0] eq "CPUSpeed") {
				# get maximum
				if ($rm_hash{ $pair[0] } < $pair[1]) { 
					$rm_hash{ $pair[0] } = $pair[1];
				}
			}
			if ($pair[0] eq "DiskSpace") {
				# get maximum
				if ($rm_hash{ $pair[0] } < $pair[1]) { 
					$rm_hash{ $pair[0] } = $pair[1];
				}
			}
			if ($pair[0] eq "OSName") {
				# SLA overwrites user constraint
				$rm_hash{ $pair[0] } = $pair[1];
			}	
		} else {
			$rm_hash{ $pair[0] } = $pair[1];

		}
	}   
	
	# check that requested constraints are supported by the API
	my %supported_constraints = ( 
		"WallClockTime"  => 1,
		"PhysicalMemory" => 1,
		"CPUSpeed"       => 0,
		"OSName"         => 1,
		"DiskSpace"      => 0,
	);

	for my $key (keys %rm_hash) {
		print "DEBUG: found constraint: $key <$rm_hash{ $key }>\n" if $debug;
		if ( $supported_constraints{$key} ) {
			die "Error: Constraint $key has no subroutine defined! stopped " unless defined &$key;
			push @constraints, &$key($rm_hash{ $key });		
		} else {
			die "Error: resource constraint $key is not a valid API resource constraint! stopped "; 

		}
	}
	return join(" ", @constraints);  
}

sub DiskSpace () {
	my $par = shift;
	print "Constraint DiskSpace($par) not applicable\n";

	return "";
}

sub OSName () {
	my $par = shift;
	print "Subroutine: OSName($par)\n";
        my $ret = "## OSName($par) 
#PBS -l arch=$par
";

	return $ret;
}

sub PhysicalMemory () {
	my $par = shift;
	print "Subroutine: PhysicalMemory($par)\n";
        my $ret = "## PhysicalMemory($par) in MB
#PBS -l mem=${par}mb
";

	return $ret;
}

sub WallClockTime () {
	my $par = shift;
	print "Subroutine: WallClockTime($par)\n";
	my $ret = "## WallClockTime constraint: ($par) sec
#PBS -l walltime=$par
	";
	   
	return $ret;
}


sub CPUSpeed () {
	my $par = shift;
	my $myCPUSpeed=0;
	my $ret = "## CPUSpeed constraint ($par) is not applicable 
	";
    
    print "Warning: cannot apply CPU speed constraint in PBS\n"; 
    
	return $ret;
}


sub extInvoke () {
	my $l_cmd = shift;
	my $l_app = shift;
	my $l_dia = 0;
	
	if($l_cmd eq "system") {
		$l_dia = system($l_app);
	} else {
	        $l_dia = exec($l_app);
	}
	
	if ($l_dia == -1) {
		print "<$l_app> failed to execute: $!\n";
	}
	elsif ($l_dia & 127) {
		printf "<$l_app> child died with signal %d, %s coredump\n",
		($l_dia & 127),  ($l_dia & 128) ? 'with' : 'without';
	}
	else {
		printf "<$l_app> child exited with value %d\n", $l_dia >> 8;
	}

	return $l_dia >> 8;
}


	
###################################################
# print usage and exit
# 

sub usage()
{
    print STDERR << "    EOF";
    
    This program submits a job to run locally

 usage: startJob 
            -d <session directory>
            -e <path to application wrapper script>
           [-r [RM instructions]...]   
           [-h ] this help message
           [-- [application arguments]...]
    

 example: $0 -d tmp -e app1 -- cputime 10
    
    EOF
    exit 0;
}


###################################################
# Print header line
#

sub printHeader()
{
   print "\n  StartJob ver: 5.0.0\n";
   print 'GRIA  Start Job wrapper ($Revision: 6548 $)';
   print "\n";
   return;
}

