#!/usr/bin/perl # $Id$ use strict; use warnings; use IPC::Open2; use Getopt::Long; use Data::Dumper; use File::Path; #use Net::SSH::Perl::Key; my $SCIRE_CONFIG_FILE = '../etc/scire.conf'; #will be /etc/scire.conf when released. my %conf; my ($SERVER_STDOUT, $SERVER_STDIN); run_main(); sub run_main { parse_command_line(); my $conf_file = (defined($conf{config})) ? $conf{config} : $SCIRE_CONFIG_FILE; read_config_file($conf_file); check_job_dir(); my $connection_command = build_connection_command(); #ok folks so here's how this thang goes down. #1. Connect. create_connection($connection_command); #2. Register with the DB. (only it knows if you're allowed to be active) # If we do not have a defined key file, we assume this is the first run of this client # so we register them instead of trying to identify. if(defined($conf{key_file}) and (-f $conf{key_file})) { if(!identify_client()) { exit(1); } } else { register_client(); exit(0); } #3. Scan the jobs directory. If there are done/failed jobs, report them. Note jobs in running or queue. my @existing_jobs; @existing_jobs = scan_jobs_dir(); #4. Fetch the jobs list get_jobs(@existing_jobs); #5. ? #run_test(); } sub run_test { for(('PING', 'FOO', 'QUIT')) { my $response = send_command($_); } } sub parse_command_line { GetOptions( 'debug|d' => \$conf{debug}, 'dry-run' => \$conf{dry_run}, 'help|h' => \$conf{help}, 'config|c=s' => \$conf{config}, 'threads|t=i' => \$conf{max_threads}, #config overrides. 'host=s' => \$conf{host}, 'port=i' => \$conf{port}, 'user|u=s' => \$conf{user}, 'server_script=s' => \$conf{server_script}, 'job_dir' => \$conf{job_dir}, ); if ($conf{help}) { print "\nusage: scireclient.pl [--debug or -d]\n\t [--dry-run]" ."\t [--config=CONF or -c] \n\t [--threads=# or -t] \t [--help or -h] \n" ."\t [[--host=HOST] \t [--port=PORT] \t [--user=USER or -u] \n\t" ." [--server_script=foo.pl] \t [--job_dir=/tmp/jobs] \n"; exit 0; } } sub send_command { my $cmd = shift; my @args = @_; my $tosend = "${cmd}"; for my $arg (@args) { if($arg =~ /^[0-9]+$/) { $tosend .= " ${arg}"; } else { $arg =~ s/"/\\"/g; $tosend .= " \"${arg}\""; } } print "Sending: ${tosend}\n" if $conf{debug}; print SERVER_STDIN "${tosend}\n"; #FIXME WE NEED A TIMEOUT HERE OF SOME SORT!! #if the server doesn't give you a newline this just hangs! my $response = get_response(); return $response; } sub get_response { # XXX: Add some logic for multi-line responses here my $response = ; print "Got: ${response}" if($conf{debug}); return $response; } sub parse_response { my $resp = shift; return "Not sure how this is gonna work yet"; } sub create_connection { # XXX: How do we capture this error? $pid has a valid value even if the # process fails to run, since it just returns the PID of the forked perl # process. I tried adding 'or die' after it, but it didn't help since it # doesn't fail in the main process. When it fails, it outputs an error # to STDERR: # open2: exec of ../server/scireserver.pl failed at ./scireclient.pl line 116 my $connection_command = shift; my $pid = open2(*SERVER_STDOUT, *SERVER_STDIN, $connection_command); } sub build_connection_command { # This will eventually be something like "ssh scire@${scireserver} /usr/bin/scireserver.pl" my $connection_command = "ssh "; if(defined($conf{port})) { $connection_command .= "-o Port=$conf{port} "; } $connection_command .= "$conf{user}\@$conf{host} $conf{server_script}"; if (-d ".svn") { # Overwrite $connection_command in the case of a dev environment for now $connection_command = "../server/scireserver.pl"; } return $connection_command; } sub check_job_dir { if (! -d $conf{job_dir}) { print "WARNING! $conf{job_dir} does not exist...creating\n"; mkpath( $conf{job_dir}, {verbose => 1, mode => 0660}) or die("Couldn't make $conf{job_dir} w/ perms 0660: $!"); } } sub read_config_file { my $conf_file = shift; open(FH, "< ${conf_file}") or die("Couldn't open the config file ${conf_file}: $!"); while () { chomp; next if /^\s*(?:#|$)/; if(/^\s*(.+?)\s*=\s*(.+?)\s*(?:#.*)?$/) { unless(defined($conf{lc($1)})) { #Don't overwrite anything specified in cmdline $conf{lc($1)} = $2; } } } close(FH) or die("Couldn't close the config file ${conf_file}: $!"); } sub register_client { my $mac = "00:11:22:33:44:55"; my $ip = "192.168.2.3"; my $response = send_command("REGISTER",$mac,$ip); die "Could not register client $mac w/ ip $ip. got $response" unless ($response =~ /OK/); print "Client registered. Status is pending.\n"; } sub identify_client { open(FILE, $conf{key_file}) or die "Couldn't open client_key $conf{key_file}: $!"; my $client_key = join("", ); close(FILE); $conf{key_type} ||= "DSA"; # my $key_obj = Net::SSH::Perl::Key->new($conf{key_type}, $client_key); # my $fingerprint = $key_obj->fingerprint(); my $fingerprint = "124567890"; my $response = send_command("IDENTIFY", $fingerprint); $response =~ /^(OK|ERROR)(?: (.+))?$/; unless ($1 and ($1 eq "OK")) { print "Could not identify to server: $response\n"; return 0; } print "Client identified\n" if $conf{debug}; return 1; } sub get_jobs { my @existing_jobs = @_; my $response = send_command("GET_JOBS", @existing_jobs); $response =~ /^(OK|ERROR)(?: (.+))?$/; unless ($1 and ($1 eq "OK")) { print "Could not get jobs list from server: $response\n"; return 0; } my $jobs = $2; $jobs =~ s/\s//g; #Remove all whitespace my @jobs_list = split(/,/, $jobs); foreach my $job (@jobs_list) { my $resp = send_command("GET_JOB",$job); open(JOBFILE, ">$conf{job_dir}/queue/${job}.job") or do { print "Could not open $conf{job_dir}/queue/${job}.job for writing: $!"; next; }; print JOBFILE parse_response($resp); close(JOBFILE); print "Fetched job $job \n" if $conf{debug}; } #This function doesn't actually need to do anything with the list of jobs, the executor handles that part. } sub scan_jobs_dir { #Scan the dirs for job files. my @existing_jobs = glob("$conf{job_dir}/queue/*"); my @failed_jobs = glob("$conf{job_dir}/failed/*"); my @done_jobs = glob("$conf{job_dir}/done/*"); #Report on those jobs needing reporting. foreach my $job_file (@failed_jobs) { $job_file =~ /(\d+)\.job/; my $jobid = $1; my $response = send_command("SET_JOB_STATUS $jobid 'Failed'"); open(FILE, $job_file) or die "Couldn't open job file $job_file: $!"; my $job_data = join("", ); close(FILE); } #may be able to use same code as above. foreach my $job_file (@done_jobs) { $job_file =~ /(\d+)\.job/; my $jobid = $1; my $response = send_command("SET_JOB_STATUS $jobid 'Done'"); } return @existing_jobs; }