# #___INFO__MARK_BEGIN__ ########################################################################## # # The Contents of this file are made available subject to the terms of # the Sun Industry Standards Source License Version 1.2 # # Sun Microsystems Inc., March, 2001 # # # Sun Industry Standards Source License Version 1.2 # ================================================= # The contents of this file are subject to the Sun Industry Standards # Source License Version 1.2 (the "License"); You may not use this file # except in compliance with the License. You may obtain a copy of the # License at http://gridengine.sunsource.net/Gridengine_SISSL_license.html # # Software provided under this License is provided on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, # WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS, # MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING. # See the License for the specific provisions governing your rights and # obligations concerning the Software. # # The Initial Developer of the Original Code is: Sun Microsystems, Inc. # # Copyright: 2008 by Sun Microsystems, Inc. # # All Rights Reserved. # ########################################################################## #___INFO__MARK_END__ package JSV; require Exporter; @ISA = qw(Exporter); # always export the following @EXPORT = qw( jsv_on_start jsv_on_verify jsv_get_env_hash jsv_get_param_hash jsv_set_param jsv_del_param jsv_sub_add_param jsv_sub_del_param jsv_accept jsv_correct jsv_reject jsv_reject_wait jsv_get_env jsv_add_env jsv_mod_env jsv_del_env jsv_is_env jsv_main ); # symbols to export on request @EXPORT_OK = qw( jsv_is_param jsv_get_param jsv_sub_is_param jsv_sub_get_param jsv_show_params jsv_show_envs jsv_send_command jsv_send_error jsv_send_env jsv_script_log jsv_log_info jsv_log_warning jsv_log_error ); # use JSV qw/:ALL/; # to import everything %EXPORT_TAGS = (ALL => [@EXPORT_OK, @EXPORT]); use strict; use warnings; use FindBin; ########################################################################## # global variables my %config = ( log_file => "/tmp/jsv_$$.log", logging_enabled => 0, head_incoming => '>>>', head_outgoing => '<<<', ); my %handler = ( on_start => sub {}, on_verify => sub { jsv_accept('Job is accepted (default handler)'); }, ENV => \&handle_command_env, PARAM => \&handle_command_param, START => \&handle_command_start, SHOW => \&handle_command_show, BEGIN => \&handle_command_begin, ); # current state, allowed values: "initialized", "started" or "verifying" my $state = 'initialized'; # values of parameters my %param = (); # values of the environment sent to this script my %env = (); ########################################################################## # functions # get or set on_start handler sub jsv_on_start { if (@_) { $handler{on_start} = $_[0]; } else { return $handler{on_start}; } } # get or set on_verify handler sub jsv_on_verify { if (@_) { $handler{on_verify} = $_[0]; } else { return $handler{on_verify}; } } sub handle_command_start { if ($state eq 'initialized') { $handler{on_start}->(); jsv_send_command('STARTED'); $state = 'started'; } else { jsv_send_error("JSV script got START command but is in state $state"); } } sub handle_command_show { jsv_show_params(); jsv_show_envs(); } sub handle_command_begin { if ($state eq 'started') { $state = 'verifying'; $handler{on_verify}->(); %env = (); %param = (); } else { jsv_send_error("JSV script got BEGIN command but is in state $state"); } } { my %is_list_command = (); @is_list_command{ qw(ac e hold_jid hold_jid_ad i l_hard l_soft M m masterq o pe q q_hard q_soft S u) } = (); sub handle_command_param { my ($key, @val) = @_; my $val = join ' ', @val; if ($state eq 'started') { if (exists $is_list_command{$key}) { # parse $val into hashref # $val contains something like 'foo=bar,baz=blub' or 'foo,bar,baz' my %h; for my $item (split /,/, $val) { my ($k,$v) = split /=/, $item; $h{$k} = $v; } $param{$key} = { %h }; } else { # not list command if ($key =~ /CMDARG\d+/ || $key eq 'CMDNAME' || $key eq 'A' || $key eq 'N' || $val !~ /,/) { # the -A option can have any content, also a comma $param{$key} = $val; } else { # oops, but this looks like a list option (contains a comma) jsv_send_error("JSV script got PARAM command for non-list option '$key' but the value '$val' looks like a list"); } } } else { jsv_send_error("JSV script got PARAM command but is in state $state"); } } } # end of closure over %is_list_command sub handle_command_env { my ($action, $key, @val) = @_; if ($state eq 'started') { if ($action eq 'ADD') { $env{$key} = join ' ', @val; } } else { jsv_send_error("JSV script got ENV command but is in state $state"); } } # makes a tailored 'deep_copy' (deep enough for the %param and %env hash) sub deep_copy { my ($from) = @_; my %h = (); for my $k (keys %$from) { my $v = $from->{$k}; if (ref $v eq 'HASH') { # copy of hash in $v $h{$k} = { %$v }; } else { $h{$k} = $v; } } return %h; } # returns a copy of the hash of the current environment (sent to this script) sub jsv_get_env_hash { return deep_copy(\%env); } sub jsv_is_env { return exists $env{$_[0]}; } sub jsv_add_env { my ($key, $val) = @_; $env{$key} = $val; jsv_send_command("ENV ADD $key $val"); } sub jsv_mod_env { my ($key, $val) = @_; $env{$key} = $val; jsv_send_command("ENV MOD $key $val"); } sub jsv_del_env { my ($key) = @_; delete $env{$key}; jsv_send_command("ENV DEL $key"); } sub jsv_get_env { my ($key) = @_; if (exists $env{$key}) { return $env{$key}; } return; } # returns a copy of the hash of the current parameters sub jsv_get_param_hash { return deep_copy(\%param); } sub jsv_is_param { return exists $param{$_[0]}; } sub jsv_get_param { my ($key) = @_; if (exists $param{$key}) { return $param{$key}; } return; } sub stringify { my ($val) = @_; if (ref $val eq 'HASH') { # stringify the hashref # { foo => 'bar', baz => 'boz' } gets stringified to 'foo=bar,baz=boz' # { foo => undef, baz => undef } gets stringified to 'foo,baz' # { foo => undef, baz => 'boz' } gets stringified to 'foo,baz=boz' $val = join(',', map { my $v = $val->{$_}; (defined $v) ? "$_=$v" : "$_"} keys %$val); } return $val; } sub jsv_set_param { my ($key, $val) = @_; $param{$key} = $val; $val = stringify($val); jsv_send_command("PARAM $key $val"); } sub jsv_del_param { my ($key) = @_; delete $param{$key}; jsv_send_command("PARAM $key"); } sub jsv_sub_is_param { my ($key, $sub) = @_; # do not autovivify => two exists checks return unless exists $param{$key}; if (exists $param{$key}{$sub}) { return 1; } return; } # returns undef or empty list if sub-parameter does not exist. sub jsv_sub_get_param { my ($key, $sub) = @_; # do not autovivify => two exists checks return unless exists $param{$key}; if (exists $param{$key}{$sub}) { return $param{$key}{$sub}; } return; } sub jsv_sub_add_param { my ($key, $sub, $val) = @_; my $href = $param{$key}; $href->{$sub} = $val; jsv_set_param($key, $href); } sub jsv_sub_del_param { my ($key, $sub) = @_; my $href = $param{$key}; delete $href->{$sub}; jsv_set_param($key, $href); } sub show_hash { my ($name, $href) = @_; my ($k,$v); while ( ($k,$v) = each %$href ) { $v = stringify($v); jsv_send_command("LOG INFO got $name: $k='$v'"); } } sub jsv_show_params { show_hash('param', \%param); } sub jsv_show_envs { show_hash('env', \%env); } sub jsv_send_command { print "@_\n"; jsv_script_log($config{head_outgoing},@_); } sub jsv_send_error { jsv_send_command('ERROR', @_); } sub jsv_send_env { jsv_send_command("SEND ENV"); } sub jsv_accept { job_handle('ACCEPT', @_); } sub jsv_correct { job_handle('CORRECT', @_); } sub jsv_reject { job_handle('REJECT', @_); } sub jsv_reject_wait { job_handle('REJECT_WAIT', @_); } sub job_handle { if ($state eq 'verifying') { jsv_send_command("RESULT STATE @_"); $state = 'initialized'; } else { jsv_send_error("JSV script will send 'RESULT STATE $_[0]' command but is in state $state"); } } { my $fh; # logging file handle my $myself = "$FindBin::Bin/$FindBin::Script"; # full path of this script # open log file and write logging header sub script_log_open { return unless $config{logging_enabled}; # don't do any error handling, if we can't log, it's not the end of the world open $fh, '>>', $config{log_file}; # no buffering of logging file handle my $old_fh = select $fh; $| = 1; select $old_fh; my $date_time = localtime(); jsv_script_log(<<"END_OF_LOG"); $myself started on $date_time This file contains logging output from a GE JSV script. Lines beginning with $config{head_incoming} contain the data which was send by a command line client or sge_qmaster to the JSV script. Lines beginning with $config{head_outgoing} contain data which is send from this JSV script to the client or sge_qmaster END_OF_LOG } # close log file and write logging footer sub script_log_close { return unless $config{logging_enabled}; my $date_time = localtime(); jsv_script_log("$myself is terminating on $date_time"); # don't do any error handling, if we can't log, it's not the end of the world close $fh; undef $fh; } sub jsv_script_log { return unless $config{logging_enabled} && $fh; # don't do any error handling, if we can't log this is not the end of the world print $fh "@_\n"; } } # end of closure over $fh and $myself sub jsv_log_info { jsv_send_command('LOG INFO', @_); } sub jsv_log_warning { jsv_send_command('LOG WARNING', @_); } sub jsv_log_error { jsv_send_command('LOG ERROR', @_); } sub jsv_main { script_log_open(); $| = 1; # no output buffering while (<>) { chomp; next if $_ eq ''; jsv_script_log("$config{head_incoming} $_"); # split on one space and do not throw any trailing fields away my @arg = split / /, $_, -1; my $cmd = shift @arg; last if $cmd eq 'QUIT'; if (exists $handler{$cmd}) { $handler{$cmd}->(@arg); } else { jsv_send_error("JSV script got unknown command '$cmd'"); } } script_log_close(); } 1; # return true