DZone Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

Snippets has posted 5883 posts at DZone. View Full User Profile

Parallel SSH Sessions

11.09.2009
| 4552 views |
  • submit to reddit
        Run the same command on lots of hosts in parallel via SSH.

Put the command(s) you want to run in a shell script called 'to_send.sh' in the current directory.
You need Parallel::ForkManager from <a href="http://search.cpan.org/dist/Parallel-ForkManager/">http://search.cpan.org/dist/Parallel-ForkManager/</a>
If you can't install it systemwide you can put ForkManager.pm in a directory called Parallel in the current directory.

#!/usr/bin/perl -w
#
#
#
use strict;
use Parallel::ForkManager;

my $max_procs = 30;
my @hosts;
my $timeout = 30;
my $remote_file = '/tmp/parallel-job.sh';
my $send_file = 'to_send.sh';

my $input = shift;
die "Usage: $0 FILE\n" if ! $input;
open (INPUT, $input) || die "Can't read '$input': $!\n";
while (defined (my $line = <INPUT>)) {

    chomp $line;
    push @hosts, $line;

}
close INPUT;

#
# hash to resolve PID's back to child specific information
#

my $pm =  new Parallel::ForkManager($max_procs);

my $left = @hosts;

# Setup a callback for when a child finishes up so we can
# get it's exit code
$pm->run_on_finish(
  sub { my ($pid, $exit_code, $ident) = @_;
    print STDERR "$ident -> finished. PID:$pid exit:$exit_code\n";
    $left--;
  }
);

$pm->run_on_start(
  sub { my ($pid,$ident)=@_;
    print STDERR "$ident -> started. PID:$pid\n";
  }
);

$pm->run_on_wait(
  sub {
    printf STDERR "Status: %d host%s left.\n", $left, $left == 1 ? "" : "s";
  },
  0.5
);

my $ssh_string = '/usr/bin/ssh -o StrictHostKeyChecking=no';

my $send_cmd = "cat <<EOT >$remote_file\n";

open (FILE, $send_file) || die "Can't read '$send_file': $!\n";
while (defined (my $line = <FILE>)) {

    $line =~ s/\$/\\\$/g;
    $send_cmd .= $line;

}
close FILE;

$send_cmd .= "rm $remote_file\nEOT\n/bin/sh $remote_file\n";

for my $child ( 0 .. $#hosts ) {

    my $host = $hosts[$child];

    my $pid = $pm->start($host) and next;

    #
    # This code is the child process
    #

    my @args = (qq~$ssh_string $host '$send_cmd'~);

    my $return = eval {

        print STDERR "Status: host:$host done:$child left:$left\n";
        local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
        alarm $timeout;
        return system(@args);
        alarm 0;

    };

    $return = 25443 if ! defined $return;

    if ( $return != 0 ) {

        my $exit_value  = $return >> 8;
        my $signal_num  = $return & 127;
        my $dumped_core = $return & 128;

        if ( $exit_value == 254 ) {
            printf "%25s : Logins disabled\n", $host;
        }
        elsif ( $exit_value == 1 ) {
            printf "%25s : Connection timed out\n", $host;
        }
        elsif ( $exit_value == 99 && $signal_num == 99 ) {
            #
            # Normally due to a password prompt or very slow host
            #
            printf "%25s : Session timed out\n", $host;
        }
        elsif ( $exit_value == 255 && $signal_num == 0 ) {
            printf "%25s : Connection failed\n", $host;
        }
        else {
            printf "%25s : SSH Returned error: [$exit_value] [$signal_num] [$dumped_core]\n", $host;
        }

    }
    print STDERR "$host -> $child finishing...\n";

    $pm->finish($child); # pass an exit code to finish

}

print STDERR "Waiting for last hosts...\n";
$pm->wait_all_children;
print STDERR "All hosts done.\n";