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); } <-- |
From: Salvador F. <sfa...@ya...> - 2012-04-17 08:16:28
|
----- Original Message ----- > From: Bryan Bueter <br...@bu...> > To: exp...@li... > Cc: > Sent: Monday, April 16, 2012 9:20 PM > Subject: [Expectperl-discuss] Yet Another Threading Example > > 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. Have you considered using Net::OpenSSH or Net::OpenSSH::Parallel? |
From: Bryan B. <br...@bu...> - 2012-04-17 12:13:53
|
> > > > ----- Original Message ----- >> From: Bryan Bueter <br...@bu...> >> To: exp...@li... >> Cc: >> Sent: Monday, April 16, 2012 9:20 PM >> Subject: [Expectperl-discuss] Yet Another Threading Example >> >> 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. > > Have you considered using Net::OpenSSH or Net::OpenSSH::Parallel? > > I'm not really trying to execute a parallel ssh, i'm trying to use expect within a thread. The example was arbitrary, you could Expect any command you wanted in this way. I'm simply looking for technical concerns related to the implementation. For example, in the "try" script bundled with IO::Tty, the parent process "syncs" with the exec by using a pipe: --> pipe(STAT_RDR, STAT_WTR) or die "Cannot open pipe: $!"; STAT_WTR->autoflush(1); $pid = fork(); die "Cannot fork" if not defined $pid; unless ($pid) { ... # Set up STDIN/OUT/ERR stuff { exec(@ARGV) }; print STAT_WTR $!+0; die "Cannot exec(@ARGV): $!"; } close STAT_WTR; $pty->close_slave(); $pty->set_raw(); # now wait for child exec (eof due to close-on-exit) or exec error my $errstatus = sysread(STAT_RDR, $errno, 256); die "Cannot sync with child: $!" if not defined $errstatus; close STAT_RDR; <-- I dont do any kind of sync in my example. Is that an issue? It doesnt appear to cause me any grief however I'm not sure what happens when I read from $pty before the child finishes setting it up. Thanks Bryan Bueter |