7 =head1 NAME $RCSfile: rexec,v $
9 Run arbitrary command on a set of machines
17 Andrew DeFaria <Andrew@ClearSCM.com>
25 Tue Jan 8 15:57:27 MST 2008
29 $Date: 2008/02/29 15:09:15 $
35 Usage: rexec [-u|sage] [-v|erbose] [-d|ebug] [-t|ype <machine type>]
39 -u|sage Print this usage
40 -v|erbose: Verbose mode
41 -d|ebug: Print debug messages
42 -t|ype: Machine type (Linux, Windows)
43 <command>: Command to execute
47 This script will perform and arbitrary command on a set of machines.
54 use Term::ANSIColor qw(:constants);
55 use POSIX ":sys_wait_h";
57 use lib "$FindBin::Bin/../lib", "$FindBin::Bin/../clearadm/lib";
66 my ($currentHost, $skip, $log);
69 usage => sub { pod2usage },
70 help => sub { pod2usage (-verbose => 2)},
71 verbose => sub { set_verbose },
72 debug => sub { set_debug },
76 my (%workerStatuses, %workerNames);
81 display BLUE . "\nInterrupted execution on $currentHost" . RESET;
85 display_nolf "Executing on " . YELLOW . $currentHost . RESET . " - "
86 . GREEN . BOLD . "S" . RESET . GREEN . "kip" . RESET . ", "
87 . CYAN . BOLD . "C" . RESET . CYAN . "ontinue" . RESET . " or "
88 . MAGENTA . BOLD . "A" . RESET . MAGENTA . "bort run" . RESET . " ("
89 . GREEN . BOLD . "s" . RESET . "/"
90 . CYAN . BOLD . "C" . RESET . "/"
91 . MAGENTA . BOLD . "a" . RESET . ")?";
94 my $answer = ReadKey (0);
97 if ($answer eq "\n") {
103 $answer = lc $answer;
105 if ($answer eq "s") {
107 display "Skipping $currentHost";
110 } elsif ($answer eq "a") {
111 display RED . "Aborting run". RESET;
115 display "Continuing...";
123 while ((my $worker = waitpid (-1, WNOHANG)) > 0) {
126 # Ignore all child deaths except for processes we started
127 next if !exists $workerStatuses{$worker};
129 $workerStatuses{$worker} = $status;
132 $SIG{CHLD} = \&workerDeath;
138 my ($cmd, $host, $prompt) = @_;
140 my ($remoteHost, @lines);
142 # Mark $currentHost for interrupt
143 $currentHost = $host;
146 $log = Logger->new (name => $host) if $opts{log};
148 verbose_nolf "Connecting to machine $host...";
150 display_nolf YELLOW . "$host:" . RESET;
153 $remoteHost = Rexec->new (
159 # Problem with creating Rexec object. Log error if logging and return.
160 if ($@ || !$remoteHost) {
162 $log->err ("Unable to connect to $host to execute command: $cmd") if $opts{log};
164 display RED . 'ERROR:' . RESET . " Unable to connect to $host to execute command: $cmd";
167 $total{ConnectFailures}++;
172 verbose " connected";
174 display UNDERLINE . "$cmd" . RESET unless $opts{quiet};
176 @lines = $remoteHost->execute ($cmd);
179 # Kick current connection
180 kill INT => $remoteHost->{handle}->pid;
183 # if ($opts{parallel} != 0) {
184 # $log->err ("Unable to connect to $host to execute command\n$cmd") if $opts{log};
186 # $total{ConnectFailures}++;
189 verbose "Disconnected from $host";
191 my $status = $remoteHost->status;
193 return ($status, @lines);
196 sub parallelize ($%) {
197 my ($cmd, %machines) = @_;
199 my $thread_count = 1;
201 foreach my $host (sort keys %machines) {
202 if ($thread_count <= $opts{parallel}) {
203 debug "Processing $host ($thread_count)";
206 if (my $pid = fork) {
207 # In parent process - record this host and its status
208 $workerNames{$pid} = $host;
210 # In spawned child...
213 debug "Starting process for $host [$pid]";
215 $workerNames{$pid} = $host;
217 # Mark currentHost for interrupt (How does this work in the presence
218 # of parallelization?).
219 $currentHost = $host;
221 my ($status, @lines) = execute $cmd, $host, $machines{$host};
223 $log = Logger->new (name => $host);
225 $log->log ($_) foreach (@lines);
230 # Wait for somebody to finish;
231 debug "Waiting for somebody to exit...";
234 debug "Reaped $workerNames{$reaped} [$reaped] (Status: $?)";
235 $workerStatuses{$reaped} = $? >> 8 if $reaped != -1;
242 my %threads = %workerNames;
244 foreach (keys %threads) {
245 if (waitpid ($_, 0) == -1) {
248 $workerStatuses{$_} = $? >> 8;
249 debug "$threads{$_} [$_] exited with a status of $workerStatuses{$_}";
253 debug "All processed completed - Status:";
256 foreach (sort keys %workerStatuses) {
257 debug "$workerNames{$_}\t[$_]:\tStatus: $workerStatuses{$_}";
262 display "Output of all executions";
263 foreach my $host (sort keys %machines) {
264 if (-f "$host.log") {
265 display "$host:$_" foreach (ReadFile ("$host.log"));
267 #unlink "$_host.log";
269 warning "Unable to find output for $host ($host.log missing)";
276 # Print the totals if interrupted
277 $SIG{INT} = \&Interrupted;
292 my $cmd = join ' ', @ARGV;
294 pod2usage ('No command specified') unless $cmd;
296 my $machines = Clearadm->new;
298 if ($opts{parallel} > 0) {
299 #parallelize ($cmd, %machines);
304 display "NOTE: Logging outputs to <host>.log" if $opts{log};
306 my $condition = $opts{type} ? "type=$opts{type}" : '';
308 foreach ($machines->SearchSystem ($condition)) {
312 my ($status, @lines) = execute $cmd, $system{name};
319 if (defined $status) {
321 $total{Executions}++;
330 $log->log ($_) foreach (@lines);
332 display $_ foreach (@lines);