# Library functions for the batch_queue library 
#
# File: batch_queue/lib/icbqs_client.tcl
# 
# $Revision$ on $Date$
# 
# Copyright (c) 1993-2003 ICEM CFD Engineering
#
# ------------------------------------------------------------------------------------

# 09 March 2004: JKA
#
#  o  This file depends on icbqs_server.tcl having been sourced in order that
#     it can get the icbqs_server_port_default proc
#
#  o  I also intend it not to rely on jobman, rather it should be used by,
#     but independent of the batch_queue namespace, just in case someone
#     prefers not to use that interface.
#
#  o  The "icbqs_client" will support multople client at once, differentiated by "client_id"
#     which will usually be the same as the calling batch_queue id (bqid), if using that namespace.
#
set i_debug 0

if {"" == [info commands mess]} {
    proc mess {text {color black}} {
	puts -nonewline $text
    }
}

## Initialize an IcBQS client. Examples args are:
##    "-server_port 6790 -servers [list $server1 $server2]
## If omitted, server_port is set to the value returned by batch_queue_icbqs_server_port_default.

proc batch_queue_icbqs_client_setup { client_id args } {
    global icbqs_client_available_servers ;# this one works by $client_id
    global icbqs_client_port              ;# this one works by $client_id
    global icbqs_client_servers_sockets   ;# this one DOES NOT
    #
    # --> get args, init'ing defaults first
    #
    set server_port [batch_queue_icbqs_server_port_default]
    set servers     "" ;#"localhost" ;# just in case ?? fixme ??
    set use_ssl     0
    
    foreach {n v} $args {
	if {"-" != [string range $n 0 0]} {
	    mess "Warning: cannot interpret tag \"$n\" in setting up IcBQS server!\n"
	    continue
	}
	set [string range $n 1 end] $v
    }
    
    #
    # --> "from __future__ import use_ssl" (secure sockets using TLS. fixme --jka)
    #
    
    if { $use_ssl && [catch { package require tls } res] } {
	mess "Warning: use of secure sockets requires the TLS extension: $res\n" red
	set use_ssl 0
    }
    
    set icbqs_client_port($client_id)              $server_port
    set icbqs_client_available_servers($client_id) [split $servers]
    
    set successful_servers_socketed ""

    foreach server $icbqs_client_available_servers($client_id) {
	set icbqs_client_servers_sockets($server,$server_port) [batch_queue_icbqs_client_get_socket $server $icbqs_client_port($client_id)]
	if {"" == $icbqs_client_servers_sockets($server,$server_port)} {
	    catch { unset icbqs_client_servers_sockets($server,$server_port) }
	    mess "Warning: could not get socket for server \"$server\" on given port...ensure server is set up!\n" red
	} else {
	    lappend successful_servers_socketed $server
	}
    }
    set icbqs_client_available_servers($client_id) $successful_servers_socketed

    mess "\nInitialized IcBQS client for server(s): \"$icbqs_client_available_servers($client_id)\"\n"
}

## Get a socket to a server <em>server</em> on port <em>port</em>.
## Returns the socket.

proc batch_queue_icbqs_client_forget_socket { server port } {
    global icbqs_client_servers_sockets
    
    if [info exists icbqs_client_servers_sockets($server,$port)] {
	set sock $icbqs_client_servers_sockets($server,$port)
	if [catch { close $sock } res] {
	    mess "Warning: had a little trouble closing the socket \"$sock\" for server \"$server\"\n"
	}	
	unset icbqs_client_servers_sockets($server,$port)
    }
}


## Get a socket to a server <em>server</em> on port <em>port</em>.
## Returns the socket.

proc batch_queue_icbqs_client_get_socket { server {port ""} } {
    global icbqs_client_servers_sockets
    
    if {"" == $server} {
	mess "Error connecting to unspecified remote host...\n" red
	return ""
    }
    
    #
    # --> Have to set it up
    #
    
    if {"" == $port} {
	#mess "No server port specified...did you expect it to exist already?\n" red
	#mess "Trying the default port...\n" red
	set port [batch_queue_icbqs_server_port_default]
    }
    
    #
    # --> The socket may exist already...
    #

    if [info exists icbqs_client_servers_sockets($server,$port)] {
	return $icbqs_client_servers_sockets($server,$port)
    }
    
    # fixme: (add option to) use tls::socket ?options? host port
    if [catch {socket $server $port} sock] {

puts "Should try to start up the server ourselves here."
	
#cool!cjo #set status [batch_queue_icbqs_client_setup_server $server]
	#if { !$status || [catch {socket $server $port} sock]} {
	    mess "Error connecting to remote host \"$server\" via port \"$port\": $sock\n" red
	    return ""
	#}
    }
    
    fconfigure $sock -buffering line
    
    set err [fconfigure $sock -error]
    if {"" != $err} {
	mess "Warning there may be an error in the current socket: $err\n" red
	return ""
    }
    
    set sockname [fconfigure $sock -sockname]
    set peername [fconfigure $sock -peername]
    
    if 0 { ; # debug block -->
	mess "On host $server found port \"$port\" to use (socket: $sock)\n"
	mess "\nCommunicating on socket \"$sock\":\n"
	mess "\tsockname: $sockname\n"
	mess "\tpeername: $peername\n\n"
    } ;      # <-- debug block
    
    #
    # --> return the socket channel id
    #
    
    return $sock
}

## Is this client <em>client_id</em> sane?

proc batch_queue_icbqs_client_is_sane { client_id } {
    global icbqs_client_available_servers
    global icbqs_client_port    
    
    if { 0 >= [llength $icbqs_client_available_servers($client_id)] } {
	mess "Warning: there were no (valid) servers set for IcBQS client \"$client_id\"\n" red
	return 0
    }
    
    #
    # --> For now, just make sure that at least one of the servers is reachable
    #
    
    foreach server $icbqs_client_available_servers($client_id) {
	
	set sock [batch_queue_icbqs_client_get_socket $server $icbqs_client_port($client_id)]
	if {"" != $sock} { return 1 }
    }

    mess "Warning: none of the servers \"$icbqs_client_available_servers($client_id)\" of client \"$client_id\" are reachable!\n" red

    return 0
}


## Set-up a server daemon on the remote server. Returns 1 for error, 0 for goodness.

proc batch_queue_icbqs_client_setup_server { remote_hosts } {
    global env
    
    set status 1    
    set serverd [file join $env(ICEM_ACN) lib batch_queue lib "icbqs_serverd"]
    
    foreach rh $remote_hosts {
	mess "Starting $serverd on $rh\n" green
	#set cmd "rsh $rh \"$serverd\""
	set cmd  "rsh -n $rh \"nohup tclsh \$ICEM_ACN/lib/batch_queue/lib/icbqs_server_setup.tcl >& /dev/null &\""
	set status [catch { exec $cmd } res]
	if $status {
	    mess "Warning: may not have been able to start the icbqs_serverd server on $rh: $res\n" red	    
	} else {
	    mess "Info: $res\n" green
	}
    }
    
    return $status
}

# Get any idle server from the list of servers available
#
proc batch_queue_icbqs_client_get_idle_server { client_id } {
    global icbqs_client_available_servers
    global icbqs_client_port i_debug connection_status
    
    set least_job_on_server -1
    set most_idle_server    "" ;# [lindex $icbqs_client_available_servers 0]
    
    foreach server $icbqs_client_available_servers($client_id) {
	
	set sock [batch_queue_icbqs_client_get_socket $server $icbqs_client_port($client_id)]

	if {"" == $sock} {
	    mess "Error: could not find socket on $server to determine idleness...\n" red
	    continue
	}
	
#	if {$i_debug == 1} {
#	    puts "--> got socket $sock for server $server to check idleness"
#	}
	puts $sock "batch_queue_icbqs_server_how_busy_are_you"

#	if {$i_debug == 1} {
#	    puts "--> got socket $sock for server $server to check idleness"
#	}

	set idleness($server) [gets $sock]
	#flush $sock ;# ?? fixme ?? NO, DON"T THINK SO! --jka	

#	if {$i_debug == 1} {
#	    puts "--> idleness for server $server is $idleness($server)"
#	}
	# now look at idleness for each; sort the hash, do something !! fixme	
	# note the return of icbqs_server_how_busy_are_you: a list of {njobs cpu_memval}
	set njobs  [lindex $idleness($server) 0]
	set cpuval [lindex $idleness($server) 1]
	
	#mess"--> the server $server has $njobs jobs and a cpuval $cpuval\n"
	
	# make this depend also on the cpuval...fixme! --jka
	
	if {(-1 == $least_job_on_server) || ($njobs < $least_job_on_server)} {
	    set most_idle_server $server
	    set least_job_on_server $njobs
	}
    }
    
    #
    # --> return the most idle server name (string)
    #

    return $most_idle_server
}


# submission stays in the batch_queue stuff...so this shouldn't do anything

proc batch_queue_icbqs_client_submit_job { args } { }


## Start a <em>cshellscript</em> job on a <em>server</em> with the token <em>job_token</em>

proc batch_queue_icbqs_client_exec_job { client_id cshellscript {server ""} { port ""} } {
    
    #
    # --> Find a lazy server to submit the job to, if not specificied
    #
    if {"" == $server} {
	set server [batch_queue_icbqs_client_get_idle_server $client_id]
	if {"" == $server} {
	    mess "Error: could not get idle server to execute script \"$cshellscript\"" red
	    return [batch_queue_job_failure]
	}
    }
    
    #
    # --> and get the port
    #
    
    set sock [batch_queue_icbqs_client_get_socket $server $port]
    if {"" == $sock} {
	mess "Error: could not open socket for job \"$cshellscript\"...\n" red
	return [batch_queue_job_failure]
    }
    
    #
    # -->
    #    
    
    #mess "Executing script \"$cshellscript\" on \"$server\" via socket \"$sock\"\n"
    
    puts $sock "$cshellscript"
    set pid [gets $sock]
    flush $sock ;# fixme??

    if { (0 >= $pid) || ![string is integer $pid] } {
	mess "Got wretched response \"$pid\" from command \"$cshellscript\"\n" red
	return [batch_queue_job_failure]
    }
    
    #
    # --> return the server and process_id in a list
    #
    
    return [list $server $pid]
}

## Kill the job on the remote host with the token <em>process_id</em>

proc batch_queue_icbqs_client_kill_remote_process { process_id server {port ""} } {
    
    if { ("" == $process_id) || ![string is integer $process_id] } {
	return [batch_queue_cmd_failure]
    }
    
    set sock [batch_queue_icbqs_client_get_socket $server $port]
    if {"" == $sock} {
	mess "Error: could not open socket for process \"$process_id\"...\n" red
	return [batch_queue_job_failure]
    }
    
    #puts "--> batch_queue_icbqs_client_kill_job: HOST=$server; PID=$process_id\n"
    
    puts $sock "batch_queue_icbqs_server_kill_process $process_id"
    puts "\n[gets $sock]\n"
    flush $sock ;# fixme??
    
    return [batch_queue_cmd_success]
}


## See if the process <em>process_id</em> exists on the remote host

proc batch_queue_icbqs_client_remote_process_exists { process_id server {port ""} } {
    
    if { ("" == $process_id) || ![string is integer $process_id] } {
	return 0
    }
    
    set process_exists 0
    
    set sock [batch_queue_icbqs_client_get_socket $server $port]
    if {"" == $sock} {
	mess "Error: could not open socket for job \"$process_id\" on \"$server\" via port \"$port\"...\n" red
	return 0 ;# it can't exists, not reachably anyway -- fixme?? --jka
    }
    
    puts $sock "batch_queue_icbqs_server_process_exists $process_id"
    set process_exists [gets $sock]
    #flush $sock ;# fixme??
    
#puts "got response \"$process_exists\" to query as to prcess existing"

    return $process_exists
}


## See if the filepath <em>process_id</em> is mounted/reachable on the remote host

proc batch_queue_icbqs_client_remote_filepath_exists { filepath server {port ""} } {
    
    if {"" == $filepath} { return 0 }
    
    set sock [batch_queue_icbqs_client_get_socket $server $port]
    if {"" == $sock} {
	mess "Error: could not open socket for on \"$server\" via port \"$port\" to check for file path...\n" red
	return 0 ;# it can't exists, not reachably anyway -- fixme?? --jka
    }
    
    puts $sock "batch_queue_icbqs_server_check_file_path $filepath"
    set filepath_exists [gets $sock]
    #flush $sock ;# fixme??
    
#puts "got response \"$filepath_exists\" to query as to filepath existing"
    
    return $filepath_exists
}


##

proc batch_queue_icbqs_client_remote_tmp_filepath_get { base extension server {port ""} } {
    
    set sock [batch_queue_icbqs_client_get_socket $server $port]
    if {"" == $sock} {
	mess "Error: could not open socket for on \"$server\" via port \"$port\" to get tmp file path...\n" red
	return 0 ;# it can't exists, not reachably anyway -- fixme?? --jka
    }
    
    puts $sock "batch_queue_icbqs_server_tmp_filepath_get $base $extension"
    set tmp_filepath [gets $sock]
    #flush $sock ;# fixme??
    
    return $tmp_filepath
}


## Get (by ftp) the file from the client at the path and return the server_path

proc batch_queue_icbqs_client_ensure_filepath_access { file_path server {port ""} } {
    
    if [batch_queue_icbqs_client_remote_filepath_exists $file_path $server $port] {
	return [batch_queue_cmd_success]
    }
    
    set base [file tail [file rootname $file_path]]
    set ext [file extension $file_path]
    
    set server_path [batch_queue_icbqs_client_remote_tmp_filepath_get $base $extension $server $port]
    
    set stat [batch_queue_ftp_open_put $server $user $passwd $client_path $server_path]
    
    return $stat
}
