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 (@machines) {
204 if ($thread_count <= $opts{parallel}) {
205 debug "Processing $system{name} ($thread_count)";
208 if (my $pid = fork) {
209 # In parent process - record this host and its status
210 display "Putting $system{name} into workerName{$pid}";
211 $workerNames{$pid} = $system{host};
213 # In spawned child...
216 debug "Starting process for $system{name} [$pid]";
218 $workerNames{$pid} = $system{name};
220 # Mark currentHost for interrupt (How does this work in the presence
221 # of parallelization?).
222 $currentHost = $system{name};
224 my ($status, @lines) = execute $cmd, $system{name};
226 $log = Logger->new (name => $system{name});
228 $log->log ($_) foreach (@lines);
233 # Wait for somebody to finish;
234 debug "Waiting for somebody to exit...";
236 display "Current status of workerNames:";
237 display $_ foreach (keys %workerNames);
240 if ($workerNames{$reaped}) {
241 display "workerNames for pid $reaped exists!";
243 display "workerNames for pid $reaped does not exist!";
245 debug "Reaped $workerNames{$reaped} [$reaped] (Status: $?)";
246 $workerStatuses{$reaped} = $? >> 8 if $reaped != -1;
253 my %threads = %workerNames;
255 foreach (keys %threads) {
256 if (waitpid ($_, 0) == -1) {
259 $workerStatuses{$_} = $? >> 8;
260 debug "$threads{$_} [$_] exited with a status of $workerStatuses{$_}";
264 debug "All processed completed - Status:";
267 foreach (sort keys %workerStatuses) {
268 debug "$workerNames{$_}\t[$_]:\tStatus: $workerStatuses{$_}";
273 display "Output of all executions";
274 foreach (@machines) {
277 if (-f "$system{name}.log") {
278 display "$system{name}:$_" foreach (ReadFile ("$system{name}.log"));
280 unlink "$system{name}.log";
282 warning "Unable to find output for $system{name} ($system{name}.log missing)";
289 # Print the totals if interrupted
290 $SIG{INT} = \&Interrupted;
305 my $cmd = join ' ', @ARGV;
307 pod2usage ('No command specified') unless $cmd;
309 my $machines = Clearadm->new;
311 if ($opts{parallel} > 0) {
312 my @machines = $machines->SearchSystem ("type='$opts{type}'");
314 parallelize ($cmd, @machines);
319 display "NOTE: Logging outputs to <host>.log" if $opts{log};
321 foreach ($machines->SearchSystem ("type='$opts{type}'")) {
325 my ($status, @lines) = execute $cmd, $system{name};
332 if (defined $status) {
334 $total{Executions}++;
343 $log->log ($_) foreach (@lines);
345 display $_ foreach (@lines);