#!/usr/bin/perl -w
use strict ;

=head1 sched_slave

A complete network scheduling system solution

=head2 DESCRIPTION

This program can execute part of job.

=head2 USAGE

    Usage : sched_slave [-h] [-t] [-c config.conf]
    --help      : print this help
    --conf f    : use f for config file
    --test	: test config file

=head2 CONFIGURATION

    [slave]
    ; master name
    master_ip=localhost

    ; master port
    master_port=5544

    ; master password
    master_passwd=toto

    ; connection retry (sec) 
    master_retry=10
    work_dir=/tmp/slave
    db_dir=/tmp/db
    hostname=plume


=head2 INSTALLATION

Complete installation details are in the README and README.conf files included
with the software. It works with the XML::Mini, LWP and Net::EasyTCP library
available on CPAN.

=head2 AUTHOR

(C) 2004-2005 Eric Bollengier

You may reach me through the contact info at eric@eb.homelinux.org

=head2 LICENSE

    sched_slave, part of the network scheduling system (Sched)
    Copyright (C) 2004-2005 Eric Bollengier All rights reserved.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

=cut



use Config::IniFiles ;
use XML::Mini ;
use Event ;

use Sched ;
use Sched::Cmd ;
use Sched::Callback ;
use Sched::Timeout ;

my $VERSION = '$Id: sched_slave,v 1.1 2005/04/05 19:45:18 mcgregor Exp $' ;

$ENV{PATH} = '/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin' ;

################################################################
# traitement de la ligne de commande

sub HELP_MESSAGE
{
    print "Usage : $0 [-h] [-t] -c config.conf
    --help      : print this help
    --conf f    : use f for config file
    --test	: test config file
" ;
    exit (1) ;
}

my $work_dir ;

my $file_conf = "$Sched::prefix_etc/slave.cfg" ;
my $test_conf ;

use Getopt::Long ;

GetOptions("conf=s"   => \$file_conf,
           "help"     => \&HELP_MESSAGE,
	   "test"     => \$test_conf) ;

if (!-r $file_conf) {
    print "E : impossible d'ouvrir le fichier de conf ($file_conf) $!\n" ;
    HELP_MESSAGE() ;
}

&Sched::init('slave', $file_conf) or die "E : erreur de configuration" ;

my %conf_default = ( );

sub valid_conf
{
    
    my $ret = 1 ;
    if (!$Sched::cfg->SectionExists('slave')) {
	$Sched::log->write("E : impossible de trouver [slave] dans $file_conf") ;
	return 0 ;
    }
    
    for my $k qw(work_dir)
    {
	if (!Sched::cfg($k)) {
	    $Sched::log->write("E : [slave]/$k is not defined");
	    $ret = 0 ;
	} elsif(! -d Sched::cfg($k)) {
	    $Sched::log->write("E : [slave]/$k is not a directory (" . Sched::cfg($k) . ")");
	    $ret = 0 ;
	} else {
	    $Sched::log->write("D : $k = " . Sched::cfg($k), 15)
		if ($test_conf) ;
	}
    }

    for my $k (keys %conf_default)
    {
	if (!Sched::cfg($k)) {
	    $Sched::log->write("E : attention [slave]/$k non defini dans $file_conf");
	    $Sched::log->write("E : $k = $conf_default{$k}") ;
	    $Sched::cfg->newval('slave', $k, $conf_default{$k}) ;
	} else {
	    $Sched::log->write("D : $k = " . Sched::cfg($k), 15)
		if ($test_conf) ;
	}
    }

    Sched::Var::add_var('__HOSTNAME__', $Sched::hostname) ;
    
    $Sched::cfg->WriteConfig(\*STDOUT) if ($test_conf) ;

   
    return $ret ;
}

sub _slave_cmd
{
    my $data = shift ;

    my $xml = new XML::Mini::Document() ;
    $xml->fromString($data) ;
    $xml = $xml->getRoot()->getElement('task') if ($xml) ;

    if (!$xml) {
	$Sched::log->write("E : impossible d'identifier la commande $data") ;
	return ;
    }

    my $serial = $xml->attribute('serial') ;
    my $sid = $xml->attribute('sid') ;

    my $cmd = new Sched::Cmd(xml => $xml,
			     serial => $serial);
			     #stdout => "stdout:$sid",
			     #stderr => "stderr:$sid") ;
    
    if (!$cmd) {
	$Sched::log->write("E : pb dans la creation du Sched::Cmd") ;
	return ;
    }

    my $host = $cmd->attribute('host') || '' ;
    if ($host ne $Sched::hostname) {
	$Sched::log->write("E : pb dans le hostname cible $host") ;
	$cmd->update_from_data("<task sid='$sid' info='mauvais host' state='255' status='finish' />");
	Sched::Net::send_to_master("CHLD " . $cmd->to_xml()) ;
	return ;
    }

    mkdir ("$work_dir/$serial") ;
    if (!chdir ("$work_dir/$serial")) {

	$cmd->update_from_data("<task sid='$sid' info='initialisation impossible' state='255' status='finish' />");
	Sched::Net::send_to_master("CHLD " . $cmd->to_xml()) ;
	return ;
    }

    $Sched::log->write("I : running $sid", 10) ;

    $cmd->run() ;
    
    chdir ("$work_dir") ;

    Sched::Timeout::add_timer($sid, $cmd->attribute('maxtime')) ;
}


sub _slave_cancel
{
    my $data = shift ;
    
    my $cmd = Sched::Cmd::get_from_sid($data) ;
    if (!$cmd) {
	my $id = $data ;
	$id =~ s/:/_/g ;
	if (-f "$work_dir/status_$id") {
	    open(FP, "$work_dir/status_$id") ;
	    my $xml = <FP> ;
	    chomp($xml) ;
	    close(FP) ;
	    Sched::Net::send_to_master("CHLD $xml") ;
	} else {
	    $Sched::log->write("E : pb dans le cancel de $data le task n'existe pas...") ;
	    Sched::Net::send_to_master("CHLD <task sid='$data' status='126' state='failed' info='task process not found on slave'/>") ;
	}
	return 0 ;
    }
    # le cmd est en cours
    $cmd->cancel() ;
}


my %proto = (
	     'PING' => {	# pas de gestion des erreurs
		 arg => undef,
		 fct => sub {
		     Sched::Net::send_to_master('OK', 'drop') ;
		 },
	     },
	     'OK' => {	# pas de gestion des erreurs
		 arg => undef,
		 fct => sub {
		 },
	     },
	     'CMD' => {
		 arg => undef,
		 fct => \&_slave_cmd,
	     },

	     'CANCEL' => {
		 arg => '^[\w\d_\.-]:[\w\d_\.-]+$',
		 fct => \&_slave_cancel,
	     },

	     'QUIT' => {
		 arg => undef,
		 fct => sub {
		     Sched::Net::_got_error() ;
		   },
	     },
	     );


sub idle_wait_func
{
    while (my $cmd = Sched::Cmd::wait()) {
	&Sched::Timeout::del_timer($cmd->attribute('sid')) ;
	my $xml = $cmd->to_xml() ;
	Sched::Net::send_to_master("CHLD $xml") ;

	my $sid = $cmd->attribute('sid') ;
	$sid =~ s/:/_/g ;

	open(FP, ">$work_dir/status_$sid") ;
	print FP $xml ;
	close(FP) ;
    }

    return 1 ;
}


valid_conf() || die "E : initialisation impossible" ;

exit 0 if ($test_conf) ;

################################################################

# main
{
    $work_dir = Sched::cfg('work_dir') ;
    chdir($work_dir) 
	|| die "E : impossible d'acceder a ($work_dir) $!" ;

    my $wait_w = Event->idle(desc => "Recuperation des task",
			     min => 5,
			     max => 120,
			     cb => \&idle_wait_func) ;
    $wait_w->start() ;
    

    my $signal_w = Event->signal(signal => 'CHLD',
				 desc => "Recuperation des Task",
				 cb => \&idle_wait_func,
				 ) ;
    $signal_w->start() ;

    Sched::Net::set_proto(%proto) ;

    Sched::Net::master_connect() ;

    my $ret = Event::loop();
}


# EOF

