|
From: Bryan B. <br...@bu...> - 2012-04-16 20:00:23
|
Ok, still playing around with threading on top of Expect, and I've
designed what I think is a pretty good solution. However there is much I
dont understand and I'm looking for feedback to make sure i'm not doing
something inherently wrong.
The way I've been able to do this is by pre-allocating pty's for jobs I'm
going to need, in this case i'm ssh'ing so i need one pty per host.
After the pty's are set up, i fork off one or more worker process who
reads in a list of command from a pipe. When the worker gets a hostname
it then forks another process who ties STDIN/OUT/ERR to the pty for that
host and exec's the ssh command.
The main process controls the threading and manages the workers. In my
example i dont do much error checking at all, but in the even the thread
returned a bad result, i could probably HUP the worker process and have it
destroy its child. That way i dont loose a worker when its child process
hangs.
Anyway, here is my code, please let me know what you think.
Thanks
Bryan Bueter
-->
#!/usr/bin/perl
use strict;
use threads;
use IO::Pty;
use Expect;
$Expect::Log_Stdout = 0;
my $prompt = '[>#\$] $';
my @hosts = ("host1", "host2", "host3", "host4", "host5", "host6",
"host7", "host8", "host9"); # Aliases to localhost
my @commands = ("uptime", "who", "who am i", "pwd");
# Pre-allocate ptys
my %ptys = ();
foreach (@hosts) { $ptys{$_} = new IO::Pty; }
# Start worker processes
my @workers = ();
my $workers = 4;
for (my $i=0; $i<$workers; $i++) {
pipe(my $from_parent, my $to_child);
unless(fork()) {
close($to_child);
worker($from_parent);
}
close($from_parent);
push(@workers, $to_child);
}
# Scan and feed worker processes hosts to spawn ssh sessions
my @threads;
while (@hosts) {
for (my $i=0; $i<@workers; $i++) {
if ( !defined($threads[$i]) || $threads[$i]->is_joinable() ) {
$threads[$i]->join() if defined($threads[$i]);
my $host = shift @hosts;
print { $workers[$i] } $host."\n";
$ptys{$host}->close_slave();
$ptys{$host}->set_raw();
$threads[$i] = threads->new("run_threaded", $host);
last;
}
}
}
# Out of hosts join everyone back in and clean up
foreach (@threads) { $_->join(); }
foreach (@workers) { close $_ ; }
wait();
## END OF MAIN ###
# Expect on the pty and run the @commands
sub run_threaded {
my $host = shift;
my $exp = Expect->exp_init($ptys{$host});
$exp->log_file("$host.log");
$exp->expect(5, '-re', $prompt) or return 0;
foreach (@commands) {
print "Executing $_ on $host\n";
$exp->send($_."\n");
$exp->expect(5, '-re', $prompt) or return 0;
}
$exp->send("exit;\nexit;\n;exit;\n");
$exp->hard_close();
return 1;
}
# Worker waits for hostnames and spawns the ssh command
sub worker {
my $pipe = shift;
while (<$pipe>) {
chomp;
my $host = $_;
unless(fork()) {
my $pty = $ptys{$host};
$pty->make_slave_controlling_terminal();
my $slave = $pty->slave();
close($pty);
$slave->clone_winsize_from(\*STDIN);
$slave->set_raw();
open(STDIN, "<&". $slave->fileno());
open(STDOUT, ">&". $slave->fileno());
open(STDERR, ">&". $slave->fileno());
close($slave);
{ exec("ssh -o StrictHostKeyChecking=no -x $host"); }
die "Cannot exec ssh: $!";
}
wait();
}
exit(0);
}
<--
|