Initial add of defaria.com
[clearscm.git] / defaria.com / GD / rexec
1 #!/usr/local/bin/perl\r
2 ################################################################################\r
3 #\r
4 # File:         $RCSfile: $\r
5 # Revision:     $Revision: $\r
6 # Description:  Remotely run processes on other machines\r
7 # Author:       Andrew@DeFaria.com\r
8 # Created:      Tue Jan  8 15:57:27 MST 2008\r
9 # Modified:     $Date: $\r
10 # Language:     perl\r
11 #\r
12 # (c) Copyright 2008, ClearSCM, Inc., all rights reserved\r
13 #\r
14 ################################################################################\r
15 use strict;\r
16 use warnings;\r
17 \r
18 use FindBin;\r
19 use Getopt::Long;\r
20 use Term::ANSIColor qw(:constants);\r
21 use POSIX ":sys_wait_h";\r
22 \r
23 my $libs;\r
24 \r
25 BEGIN {\r
26   $libs = $ENV{SITE_PERLLIB} ? $ENV{SITE_PERLLIB} : "$FindBin::Bin/../lib";\r
27 \r
28   die "Unable to find libraries\n" if !$libs and !-d $libs;\r
29 }\r
30 \r
31 use lib "$FindBin::Bin/../lib";\r
32 use lib $libs;\r
33 \r
34 use Display;\r
35 use Logger;\r
36 use Machines;\r
37 use Rexec;\r
38 use Utils;\r
39 \r
40 our $_host;\r
41 our $_skip                      = 0;\r
42 our $_currentHost;\r
43 \r
44 my $_log                        = 0;\r
45 my $_quiet                      = 0;\r
46 my $_alternateFile;\r
47 my $_parallel                   = 0;\r
48 \r
49 my $_totalMachines              = 0;\r
50 my $_totalExecutions            = 0;\r
51 my $_totalFailures              = 0;\r
52 my $_totalConnectFailures       = 0;\r
53 my $_totalSkips                 = 0;\r
54 \r
55 my (%_workerStatuses, %_workerNames);\r
56 \r
57 sub Usage {\r
58   my $msg = shift;\r
59 \r
60   display "ERROR: $msg\n" if defined $msg;\r
61 \r
62   display "rexec\t[-v] [-d] [-u] <cmd>";\r
63   display "\t-v\tTurn on verbose mode";\r
64   display "\t-d\tTurn on debug mode";\r
65   display "\t-u\tThis usage message";\r
66   display "<cmd>\tCommand to execute remotely";\r
67 \r
68   exit 1;\r
69 } # Usage\r
70 \r
71 sub printStats {\r
72   display YELLOW  . "Machines: "                . RESET . "$_totalMachines " .\r
73           MAGENTA . "Executions/Failures: "     . RESET . "($_totalExecutions/$_totalFailures) " .\r
74           BLUE    . "Connect Failures/Skips: "  . RESET . "($_totalConnectFailures/$_totalSkips)";\r
75 } # printStats\r
76 \r
77 sub Interrupted {\r
78   use Term::ReadKey;\r
79 \r
80   display BLUE . "\nInterrupted execution on $_host" . RESET;\r
81 \r
82   printStats;\r
83 \r
84   display_nolf "Executing on " . YELLOW . $_host . RESET . " - "\r
85     . GREEN     . BOLD . "S" . RESET . GREEN    . "kip"         . RESET . ", "\r
86     . CYAN      . BOLD . "C" . RESET . CYAN     . "ontinue"     . RESET . " or "\r
87     . MAGENTA   . BOLD . "A" . RESET . MAGENTA  . "bort run"    . RESET . " ("\r
88     . GREEN     . BOLD . "s" . RESET . "/"\r
89     . CYAN      . BOLD . "C" . RESET . "/"\r
90     . MAGENTA   . BOLD . "a" . RESET . ")?";\r
91 \r
92   ReadMode ("cbreak");\r
93   my $answer = ReadKey (0);\r
94   ReadMode ("normal");\r
95 \r
96   if ($answer eq "\n") {\r
97     display "c";\r
98   } else {\r
99     display $answer;\r
100   } # if\r
101 \r
102   $answer = lc $answer;\r
103 \r
104   if ($answer eq "s") {\r
105     *STDOUT->flush;\r
106     display "Skipping $_host";\r
107     $_skip = 1;\r
108     $_totalSkips++;\r
109   } elsif ($answer eq "a") {\r
110     display RED . "Aborting run". RESET;\r
111     printStats;\r
112     exit;\r
113   } else {\r
114     display "Continuing...";\r
115     $_skip = 0;\r
116   } # if\r
117 } # Interrupted\r
118 \r
119 sub workerDeath {\r
120   while ((my $worker = waitpid (-1, WNOHANG)) > 0) {\r
121     my $status  = $?;\r
122 \r
123     # Ignore all child deaths except for processes we started\r
124     next if !exists $_workerStatuses{$worker};\r
125 \r
126     $_workerStatuses{$worker} = $status;\r
127   } # while\r
128 \r
129   $SIG{CHLD} = \&workerDeath;\r
130 } # workerDeath\r
131 \r
132 sub execute ($$$) {\r
133   my ($cmd, $host, $prompt) = @_;\r
134 \r
135   my @lines;\r
136 \r
137   verbose_nolf "Connecting to machine $host...";\r
138 \r
139   eval {\r
140     $_currentHost = new Rexec (\r
141       host      => $host,\r
142       prompt    => $prompt,\r
143     );\r
144   };\r
145 \r
146   # Problem with creating Rexec object. Log error if logging and return.\r
147   if ($@ or !$_currentHost) {\r
148     if ($_log) {\r
149       my $log = new Logger (name => $_host);\r
150 \r
151       $log->err ("Unable to connect to $host to execute command\n$cmd");\r
152     } # if\r
153 \r
154     $_totalConnectFailures++;\r
155 \r
156     return (1, ());\r
157   } # if\r
158 \r
159   verbose " connected";\r
160 \r
161   display YELLOW . "$host:" . RESET . UNDERLINE . "$cmd" . RESET unless $_quiet;\r
162 \r
163   @lines = $_currentHost->exec ($cmd);\r
164 \r
165   if ($_skip) {\r
166     # Kick current connection\r
167     kill INT => $_currentHost->{handle}->pid;\r
168   } # if\r
169 \r
170   if ($_parallel != 0) {\r
171     if ($_log) {\r
172       my $log = new Logger (name => $_host);\r
173 \r
174       $log->err ("Unable to connect to $host to execute command\n$cmd");\r
175     } # if\r
176 \r
177     $_totalConnectFailures++;\r
178   } # if\r
179 \r
180   verbose "Disconnected from $host";\r
181 \r
182   my $status = $_currentHost->status;\r
183 \r
184   undef $_currentHost;\r
185 \r
186   return ($status, @lines);\r
187 } # execute\r
188 \r
189 sub parallelize ($%) {\r
190   my ($cmd, %machines) = @_;\r
191 \r
192   my $thread_count = 1;\r
193 \r
194   foreach $_host (sort keys %machines) {\r
195     if ($thread_count <= $_parallel) {\r
196       debug "Processing $_host ($thread_count)";\r
197       $thread_count++;\r
198 \r
199       if (my $pid = fork) {\r
200         # In parent process - record this host and its status\r
201         $_workerNames{$pid} = $_host;\r
202       } else {\r
203         # In spawned child...\r
204         $pid = $$;\r
205 \r
206         debug "Starting process for $_host [$pid]";\r
207 \r
208         $_workerNames{$pid} = $_host;\r
209         \r
210         my ($status, @lines) = execute $cmd, $_host, $machines{$_host};\r
211 \r
212         my $log = new Logger (name => $_host);\r
213 \r
214         $log->log ($_) foreach (@lines);\r
215 \r
216         exit $status;\r
217       } # if\r
218     } else {\r
219       # Wait for somebody to finish;\r
220       debug "Waiting for somebody to exit...";\r
221       my $reaped = wait;\r
222 \r
223       debug "Reaped $_workerNames{$reaped} [$reaped] (Status: $?)";\r
224       $_workerStatuses{$reaped} = $? >> 8 if $reaped != -1;\r
225 \r
226       $thread_count--;\r
227     } # if\r
228   } # foreach\r
229 \r
230   # Wait for all kids\r
231   my %threads = %_workerNames;\r
232 \r
233   foreach (keys %threads) {\r
234     if (waitpid ($_, 0) == -1) {\r
235       delete $threads{$_};\r
236     } else {\r
237       $_workerStatuses{$_} = $? >> 8;\r
238       debug "$threads{$_} [$_] exited with a status of $_workerStatuses{$_}";\r
239     } # if\r
240   } # foreach\r
241 \r
242   debug "All processed completed - Status:";\r
243 \r
244   if (get_debug) {\r
245     foreach (sort keys %_workerStatuses) {\r
246       debug "$_workerNames{$_}\t[$_]:\tStatus: $_workerStatuses{$_}";\r
247     } # foreach\r
248   } # if\r
249 \r
250   # Gather output...\r
251   display "Output of all executions";\r
252   foreach $_host (sort keys %machines) {\r
253     if (-f "$_host.log") {\r
254       display "$_host:$_" foreach (ReadFile ("$_host.log"));\r
255 \r
256       #unlink "$_host.log";\r
257     } else {\r
258       warning "Unable to find output for $_host ($_host.log missing)";\r
259     } # if\r
260   } # foreach\r
261 } # parallelize\r
262 \r
263 # Print the totals if interrupted\r
264 $SIG{INT} = \&Interrupted;\r
265 \r
266 # Get our options\r
267 GetOptions (\r
268   "usage"       => sub { Usage "" },\r
269   "verbose"     => sub { set_verbose },\r
270   "debug"       => sub { set_debug },\r
271   "log"         => \$_log,\r
272   "quiet"       => \$_quiet,\r
273   "file=s"      => \$_alternateFile,\r
274   "parallel:i"  => \$_parallel,\r
275 ) || Usage "Unknown parameter";\r
276 \r
277 my $cmd = join " ", @ARGV;\r
278 \r
279 error "No command specified", 1 if !$cmd;\r
280 \r
281 my $machines = Machines->new (file => $_alternateFile);\r
282 my %machines = $machines->all ();\r
283 \r
284 if ($_parallel > 0) {\r
285   parallelize ($cmd, %machines);\r
286   printStats;\r
287   exit;\r
288 } # if\r
289 \r
290 display "NOTE: Logging output to <host>.log" if $_log;\r
291 \r
292 foreach $_host (sort keys (%machines)) {\r
293   $_totalMachines++;\r
294 \r
295   my ($status, @lines) = execute $cmd, $_host, $machines{$_host};\r
296 \r
297   if ($_skip) {\r
298     $_skip = 0;\r
299     next;\r
300   } # if\r
301 \r
302   if (defined $status) {\r
303     if ($status == 0) {\r
304       $_totalExecutions++;\r
305     } else {\r
306       if ($_log) {\r
307         my $log = new Logger (name => $_host);\r
308 \r
309         $log->log ("Host: $_host\nCommand: $cmd\nStatus: $status\nOutput:\n");\r
310         $log->log ($_) foreach (@lines);\r
311       } # if\r
312         \r
313       $_totalFailures++;\r
314 \r
315       next;\r
316     } # if\r
317   } # if\r
318 \r
319   if ($_log) {\r
320     my $log = new Logger (name => $_host);\r
321 \r
322     $log->log ("Host: $_host\nCommand: $cmd\nStatus: $status\nOutput:\n");\r
323     $log->log ($_) foreach (@lines);\r
324   } else {\r
325     display $_ foreach (@lines);\r
326   } # if\r
327 } # foreach\r
328 \r
329 printStats;\r