1 | #!/usr/bin/perl |
---|
2 | |
---|
3 | # assume a Perl script in charge of compiling MAD on the Windows platform |
---|
4 | # is waiting on an agreed-upon socket => the Server must be started before |
---|
5 | # the Client, if not the program should return an error otherwise it would |
---|
6 | # wait for ever! |
---|
7 | |
---|
8 | # accept an argument manual-trigger or automatic-trigger, the former meaning we |
---|
9 | # immediately trigger the compilation, the later meaning the program will listen |
---|
10 | # to port 7075 for a request emanating from the automatic build and test procedure. |
---|
11 | |
---|
12 | #$debug = 'no'; # global variable also used by notify() subroutine and others |
---|
13 | # this program only for tests => set $debug='yes' from 10 June 2009 |
---|
14 | $debug = 'yes'; |
---|
15 | |
---|
16 | |
---|
17 | if ($#ARGV!=0){ |
---|
18 | die "expect one argument: 'now' or 'wait-for-trigger'\n"; |
---|
19 | } else { |
---|
20 | if ($ARGV[0] eq 'now'){ |
---|
21 | $mode = 'now'; |
---|
22 | } else { |
---|
23 | if ($ARGV[0] eq 'wait-for-trigger') { |
---|
24 | $mode = 'wait-for-trigger'; |
---|
25 | } else { |
---|
26 | die "incorrect argument: should be either 'now' or 'wait-for-trigger'\n"; |
---|
27 | } |
---|
28 | } |
---|
29 | |
---|
30 | } |
---|
31 | |
---|
32 | |
---|
33 | # KILL ITSELF IN CASE ALREADY RUNNING |
---|
34 | |
---|
35 | my @check = `ps -aef | grep MadWindowsCompileClient.pl`; |
---|
36 | foreach $line (@check){ |
---|
37 | chop $line; |
---|
38 | $line =~ /^\w+[\s\t]+(\d+)/; # format of output: username pid parent ... |
---|
39 | my $pid = $1; # pid of the process being considered |
---|
40 | my $myPid = $$; # $$ is the pid of this process (special perl variable!#@?) |
---|
41 | |
---|
42 | if ($pid ne $myPid){ |
---|
43 | # check this is indeed a perl command |
---|
44 | if ($line =~ /\/usr\/bin\/perl[\s\t]/) { |
---|
45 | # `kill -9 $pid`; # kill process, unless this is this process's pid |
---|
46 | # no: instead should kill itself |
---|
47 | if ($mode eq 'now') { |
---|
48 | my $warning = "MadWindowsCompileClient.pl already running => abort new process.\n"; |
---|
49 | print $warning; |
---|
50 | notify($warning); |
---|
51 | } # otherwise don't print the message otherwise the cron job |
---|
52 | # will send an e-mail |
---|
53 | exit; |
---|
54 | } else { |
---|
55 | # skip |
---|
56 | } |
---|
57 | } # later-on should send a signal that this process would either accept |
---|
58 | # to kill itself, or reject in case it is already engaged in a compilation |
---|
59 | # on the Windows platform => before dying, close the listening socket port |
---|
60 | } |
---|
61 | |
---|
62 | |
---|
63 | |
---|
64 | # fork process to spawn a branch that will periodically refresh the AFS kerberos tokens |
---|
65 | my $child_pid = fork(); |
---|
66 | |
---|
67 | if (not defined $child_pid){ |
---|
68 | notify("no system resources to fork process => exit"); |
---|
69 | exit; |
---|
70 | } |
---|
71 | |
---|
72 | if ($child_pid==0){ |
---|
73 | # this is the child process |
---|
74 | # refresh the AFS token every 6 hours. Otherwise the token |
---|
75 | # would expire after 25 hours. |
---|
76 | # (note that this trick works for up to 10 days according to IT support) |
---|
77 | my $start = localtime; |
---|
78 | # the recipe for refresing AFS/Kerberos tokens through a child process is known to work |
---|
79 | # up to ten days. As a consequence, this process should die gracefully before ten days |
---|
80 | # of running and wait for the acrontab to relaunch it with a time of grain of 5 min |
---|
81 | # Hence the NFS client that triggs the remote compilation should have a time-out of the |
---|
82 | # same order. |
---|
83 | my $counter = 0; |
---|
84 | while(1){ |
---|
85 | $counter ++; # increment every 6 hours |
---|
86 | if ($counter > (4*5)) { # life-expectancy set to 5 days (could as well try with 9 days) |
---|
87 | # time to die gracefully and let the acrontab restart a new process with |
---|
88 | # fresh AFS and Kerberos tokens. In the meantime, the trigger should be able |
---|
89 | # to wait for the client socket port to reappear... |
---|
90 | # should also kill the parent process... |
---|
91 | my $parent_pid = getppid(); |
---|
92 | kill 9, $parent_pid; |
---|
93 | exit; |
---|
94 | } |
---|
95 | my $now = localtime; |
---|
96 | sleep 21600; # 6 hours |
---|
97 | `/usr/sue/bin/kinit -R`; |
---|
98 | `/usr/bin/aklog`; |
---|
99 | # check if the child process' parent is dead. If so, should kill itself |
---|
100 | my $parent_pid = getppid(); # get parent's pid |
---|
101 | $cnt = kill 0, $parent_pid; |
---|
102 | if ($cnt == 0){ |
---|
103 | exit; |
---|
104 | } |
---|
105 | } |
---|
106 | |
---|
107 | } |
---|
108 | |
---|
109 | # else ... |
---|
110 | |
---|
111 | if ($child_pid){ |
---|
112 | # non-zero pid means we are in the parent process, which received the child's pid |
---|
113 | |
---|
114 | |
---|
115 | |
---|
116 | # output of this program on stdout: SUCCESS or FAILURE:<message> |
---|
117 | |
---|
118 | # this program must run on a machine such as abcopl1 on which NFS mounted partitions |
---|
119 | # can be viewed through Samba by the remote Windows machine. |
---|
120 | |
---|
121 | my $windowsHost = 'abpc10788'; |
---|
122 | # $windowsHost = 'abcopl1'; # 29 september 2009 - for test purposes |
---|
123 | |
---|
124 | $executablesAfsWebFolder = "/afs/cern.ch/user/n/nougaret/www/mad/windows-binaries"; # global |
---|
125 | |
---|
126 | $madForWindowsSambaFolder = "/user/nougaret/MAD-X-WINDOWS/madX"; |
---|
127 | # problem: won't be seen on pcslux99!!! => cannot automate fully !!! |
---|
128 | # => for the time-being this process will need to be launched manually. |
---|
129 | |
---|
130 | # where binaries are delivered on the web for subsequent retreival by users |
---|
131 | |
---|
132 | $madWindowsCompilationDir = $madForWindowsSambaFolder; # global used by other routines |
---|
133 | $madWindowsDeliveryDir = "/afs/cern.ch/user/n/nougaret/www/mad/windows-binaries"; # global |
---|
134 | # also used by other routines |
---|
135 | @windowsTargets = ('madx.exe','madxp.exe','mpars.exe'); # Windows/DOS deliverables |
---|
136 | # above is global as used by other routines as well |
---|
137 | |
---|
138 | use IO::Socket::INET; |
---|
139 | use MIME::Lite; |
---|
140 | use Sys::Hostname; |
---|
141 | |
---|
142 | |
---|
143 | # two local and remote ports for communication with the Windows host in the two directions |
---|
144 | $socketPortWindows = 7070; # agreed-up with client (>1024 for non-root) |
---|
145 | $socketPortLinux = 7071; # could be the same as above |
---|
146 | # one local port to listen for requests emanating from the automated build and test process |
---|
147 | my $listeningPort = 7075; |
---|
148 | |
---|
149 | |
---|
150 | my $thisLinuxHost = hostname; |
---|
151 | |
---|
152 | |
---|
153 | # before asking the Windows host to trigger the compilation, we must first make sure that the |
---|
154 | # Samba folder MAD-X-WINDOWS/madX contains the latest CVS (more precisely latest released tagged |
---|
155 | # version - for the time being, we'll simply pick-up the latest contents of the repository) |
---|
156 | # ... well the `cvs update` is actually carried-out in sub updateMadForWindowsSambaFolder() !!! |
---|
157 | |
---|
158 | # wait to be waken-up (or start right now if mode is 'now') |
---|
159 | |
---|
160 | INFINITE_LOOP: while(1){ |
---|
161 | |
---|
162 | my $invokeCompilation = 0; # default = do nothing |
---|
163 | |
---|
164 | if ($mode eq 'wait-for-trigger'){ |
---|
165 | my $listeningSocket = new IO::Socket::INET( |
---|
166 | LocalHost => $thisLinuxHost, |
---|
167 | LocalPort => $listeningPort, |
---|
168 | Proto => 'tcp', |
---|
169 | Listen => 1, |
---|
170 | Reuse => 1, |
---|
171 | ) or die "Can't bind : $@\n"; |
---|
172 | |
---|
173 | unless ($listeningSocket) { |
---|
174 | notify("failed to open TCP socket $listeningPort on $thisLinuxHost to receive command => will die\n"); |
---|
175 | die 'failed to open TCP socket $listeningPort on $thisLinuxHost'; |
---|
176 | } |
---|
177 | |
---|
178 | my $receive = $listeningSocket->accept(); |
---|
179 | while (<$receive>){ |
---|
180 | if (/^Trigger Windows compilation$/){ |
---|
181 | # sent triggering signal via socket to Windows compilation server |
---|
182 | $invokeCompilation = 1; |
---|
183 | last; |
---|
184 | } |
---|
185 | } |
---|
186 | close $listeningSocket; |
---|
187 | } # else mode is 'now' and we should trigger the remote compilation right now |
---|
188 | |
---|
189 | |
---|
190 | if ($mode eq 'now'){ |
---|
191 | $invokeCompilation = 1 ; |
---|
192 | } |
---|
193 | |
---|
194 | |
---|
195 | if ($invokeCompilation == 1){ |
---|
196 | notify("MadWindowsCompileClient.pl will now forward the compilation request to the Windows host machine."); |
---|
197 | |
---|
198 | updateMadForWindowsSambaFolder(); |
---|
199 | |
---|
200 | |
---|
201 | |
---|
202 | # $thisLinuxHost = 'abcopl1'; |
---|
203 | # print "the Linux box is '$thisLinuxHost'\n"; |
---|
204 | |
---|
205 | my $sock = new IO::Socket::INET ( |
---|
206 | PeerAddr => $windowsHost, |
---|
207 | PeerPort => $socketPortWindows, |
---|
208 | Proto => 'tcp' |
---|
209 | ); |
---|
210 | |
---|
211 | unless ($sock) { |
---|
212 | notify("Could not create socket $socketPortWindows to connect to $windowsHost => will die\n"); |
---|
213 | die "Could not create socket: $!\n" unless $sock; |
---|
214 | } |
---|
215 | |
---|
216 | print "will now send message to port $socketPortWindows of $windowsHost\n"; |
---|
217 | |
---|
218 | print $sock "$thisLinuxHost asks: Compile MAD for Windows!\n"; |
---|
219 | |
---|
220 | $startTime = localtime; # global |
---|
221 | $endTime; # global, will be set later-on |
---|
222 | |
---|
223 | close($sock); |
---|
224 | |
---|
225 | # now wait for the message signalling that the compilation completed |
---|
226 | |
---|
227 | |
---|
228 | |
---|
229 | my $clientSock = new IO::Socket::INET( |
---|
230 | LocalHost => $thisLinuxHost, |
---|
231 | LocalPort => $socketPortLinux, |
---|
232 | Proto => 'tcp', |
---|
233 | Listen => 1, |
---|
234 | Reuse => 1 |
---|
235 | ); |
---|
236 | |
---|
237 | die "Could not create client socket: $!\n" unless $clientSock; |
---|
238 | |
---|
239 | print "$thisLinuxHost accepts messages sent through socket $socketPortLinux\n"; |
---|
240 | my $newClientSock = $clientSock->accept(); |
---|
241 | |
---|
242 | |
---|
243 | |
---|
244 | while (<$newClientSock>){ |
---|
245 | print $_; |
---|
246 | if (/Compilation completed/){ |
---|
247 | $endTime = localtime; |
---|
248 | print "OK: the compilation completed on Windows side\n"; |
---|
249 | checkWindowsCompilationOutcome(); |
---|
250 | print "=> installed the executables in the AFS web folder\n"; |
---|
251 | last INFINITE_LOOP; # leave the while loop |
---|
252 | } |
---|
253 | |
---|
254 | # should leave loop (timeout) in case there's no reply by the Windows-side server, |
---|
255 | # in which case, the executables will need to be delivered manually |
---|
256 | |
---|
257 | } |
---|
258 | |
---|
259 | close ($clientSock); |
---|
260 | |
---|
261 | } # if $invokeCompilation == 1 |
---|
262 | |
---|
263 | if ($mode eq 'now') { |
---|
264 | # should compile only once and then leave the infinite loop to complete the program |
---|
265 | last; |
---|
266 | } else { |
---|
267 | print "now wait for wake-up by next compilation-triggering signal\n"; |
---|
268 | } |
---|
269 | |
---|
270 | # debug: |
---|
271 | my $whereAmI = `pwd`; |
---|
272 | notify("at the end of the compilation, the Linux box client is in '$whereAmI'\n"); |
---|
273 | |
---|
274 | } # while(1): wait forever to be woken-up by automated build-and-test program |
---|
275 | |
---|
276 | # do we really need to kill the child process? |
---|
277 | # in principle not, but the child would commit suicide only 6 hours later due to loop duration |
---|
278 | kill 9, $child_pid; |
---|
279 | |
---|
280 | } # this is the parent process (not the child forked process refreshing AFS/Kerberos tokens) |
---|
281 | |
---|
282 | |
---|
283 | sub checkWindowsCompilationOutcome { |
---|
284 | my $initialDir = `pwd`; |
---|
285 | # check the delivery directory contents |
---|
286 | foreach $target (@windowsTargets){ |
---|
287 | # check that the executable has been created within the last hour |
---|
288 | my $ls = `ls -l $madWindowsCompilationDir/$target`; |
---|
289 | |
---|
290 | # debug |
---|
291 | notify("for target '$target', we see : '$ls'"); |
---|
292 | |
---|
293 | # pick the date and time at which the executables have been created |
---|
294 | $ls =~ /(\w{3})[\s\t]+(\d{1,2})[\s\t]+(\d+:\d+)[\s\t]/ ; |
---|
295 | |
---|
296 | my $month = $1; |
---|
297 | my $day = $2; |
---|
298 | my $time = $3; |
---|
299 | |
---|
300 | my $now = localtime; |
---|
301 | print "now=$now\n"; |
---|
302 | |
---|
303 | $now =~ /^\w{3}[\s\t]+(\w{3})[\s\t]+(\d{1,2})[\s\t]+(\d+:\d+:)\d+/ ; |
---|
304 | # forget about the year... |
---|
305 | |
---|
306 | my $monthNow = $1; |
---|
307 | my $dayNow = $2; |
---|
308 | my $time = $3; |
---|
309 | |
---|
310 | # debug |
---|
311 | notify("monthNow is '$monthNow', month is '$month', dayNow is '$dayNow', day is '$day'"); |
---|
312 | # if (0){ # for the time being, always deliver the executables, without checking anything |
---|
313 | if (($monthNow != $month)||($dayNow != $day)){ |
---|
314 | print "Mistmatch of day and month => executables were not created\n"; |
---|
315 | } else { |
---|
316 | # now check that compilation occured within on hour from now |
---|
317 | |
---|
318 | # now install the executables in the AFS web folder |
---|
319 | my $source = "$madWindowsCompilationDir/$target"; |
---|
320 | my $destination = "$madWindowsDeliveryDir/$target"; |
---|
321 | my $result = `cp $source $destination`; |
---|
322 | |
---|
323 | # debug: |
---|
324 | notify("just copied '$source' into '$destination' => outcome = '$result'"); |
---|
325 | |
---|
326 | } |
---|
327 | |
---|
328 | |
---|
329 | } # for each $target (@windowsTargets) |
---|
330 | |
---|
331 | # if everything ok... |
---|
332 | |
---|
333 | |
---|
334 | |
---|
335 | |
---|
336 | # now notify that the Windows executables are ready |
---|
337 | my $grepVersion = `grep myversion $madWindowsCompilationDir/madxd.h`; # hard-coded !? |
---|
338 | |
---|
339 | # debug: |
---|
340 | notify("now grep my version in '$madWindowsCompilationDir/madx.h'"); |
---|
341 | |
---|
342 | $grepVersion =~ /MAD-X (\d+\.\d+\.\d+)/; |
---|
343 | $madVersion = $1; # global, also used in subroutine 'deliverHtmlPage'; |
---|
344 | |
---|
345 | deliverHtmlPage(); |
---|
346 | |
---|
347 | |
---|
348 | if ($debug eq 'no') { |
---|
349 | my $msg = MIME::Lite->new( |
---|
350 | From => 'Jean-Luc.Nougaret@cern.ch', |
---|
351 | To => 'mad-windows-watchers@cern.ch', |
---|
352 | # To => 'Jean-Luc.Nougaret@cern.ch', |
---|
353 | Subject => 'MAD-X for Windows updated', |
---|
354 | Data => "Dear colleagues,\n\nPlease take note that MAD-X version $madVersion is now available on Windows.\n\nThe new releases are available for download on the new Web page:\nhttps://test-mad-automation.web.cern.ch/test-mad-automation/windows-binaries/executables.htm\n\nRegards,\nJean-Luc" |
---|
355 | ); |
---|
356 | $msg->send; |
---|
357 | } else { |
---|
358 | notify("MAD-X for Windows has been updated"); |
---|
359 | } |
---|
360 | |
---|
361 | chdir($initialDir); |
---|
362 | |
---|
363 | } # subroutine checkWindowsCompilationOutcome |
---|
364 | |
---|
365 | |
---|
366 | sub deliverHtmlPage { |
---|
367 | |
---|
368 | # at this stage, the Windows binaries have been delivered to the |
---|
369 | # AFS web folder already |
---|
370 | |
---|
371 | my $htmlFile = "$executablesAfsWebFolder/executables.htm"; |
---|
372 | # only for Windows? |
---|
373 | |
---|
374 | my $contents =''; # blank at first |
---|
375 | |
---|
376 | # grep size of the binaries located in the AFS web folder |
---|
377 | my @binaries = `ls -l $executablesAfsWebFolder/*.exe`; |
---|
378 | |
---|
379 | # my $nBinaries = scalar(@binaries); |
---|
380 | # notify("in '$executablesAfsWebFolder', 'found $nBinaries'"); |
---|
381 | |
---|
382 | $contents .= "Version $madVersion compiled with Lahey Fortran and Microsoft Visual C++:\n"; |
---|
383 | $contents .= "<table width=\"75%\" border=\"0\">\n"; |
---|
384 | my $oddOrEven = 'even'; # to colorize successive lines differently |
---|
385 | foreach $binary (@binaries){ |
---|
386 | chop $binary; # end of line |
---|
387 | # notify("line:$binary"); |
---|
388 | # notify("in '$executablesAfsWebFolder', 'found $binary'"); |
---|
389 | # -rw-r--r-- 1 nougaret pz 658664 Oct 1 12:06 /afs/cern.ch/user/n/nougaret/www/mad/windows-binaries/mpars.exe |
---|
390 | $binary =~ /(\d+)[\s\t]+(\w{3})[\s\t]+(\d{1,2})[\s\t]+(\d+:\d+)[\s\t]+[^\s]+\/(\w+\.exe)$/; |
---|
391 | my $size = $1; |
---|
392 | my $megabytes = $size / 1000000; |
---|
393 | my $month = $2; |
---|
394 | my $day = $3; |
---|
395 | my $time = $4; |
---|
396 | my $executable = $5; |
---|
397 | # notify("size='$size',exec='$executable',descr='$description{$executable}'"); |
---|
398 | $description{'madx.exe'} = "standard version"; |
---|
399 | $description{'madxp.exe'} = "version including PTC"; |
---|
400 | $description{'mpars.exe'} = "\"parser-only\" version"; |
---|
401 | if ($oddOrEven eq 'odd'){ |
---|
402 | $oddOrEven = 'even'; |
---|
403 | } else { |
---|
404 | $oddOrEven = 'odd'; |
---|
405 | } |
---|
406 | $contents .= "<tr class=\"$oddOrEven\"><td>Download</td><td><a href=\"./$executable\">$executable</a></td><td>($megabytes Megabytes)</td><td>for the $description{$executable}.</td></tr>\n"; |
---|
407 | } |
---|
408 | $contents .= "</table>\n"; |
---|
409 | |
---|
410 | # create web page in the correct AFS web folder location |
---|
411 | my $html = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">'; |
---|
412 | $html .= "<html>\n"; |
---|
413 | $html .= "<head>\n"; |
---|
414 | $html .= "<title>MAD-X downloadable executables</title>\n"; |
---|
415 | $html .= "<link rel=stylesheet href=\"../MadTestWebStyle.css\" type=\"text/css\">"; # CSS stylesheet one level up |
---|
416 | $html .= "</head>\n"; |
---|
417 | $html .= "<!-- generated by Windows compilation script -->\n"; |
---|
418 | $html .= "<body>\n"; |
---|
419 | $html .= "<p>Windows compilation started $startTime, ended $endTime</p>\n"; |
---|
420 | $html .= $contents; |
---|
421 | $html .= "</body>\n"; |
---|
422 | $html .= "</html>\n"; |
---|
423 | open(OUTHTML, ">$htmlFile"); |
---|
424 | print OUTHTML $html; |
---|
425 | close OUTHTML; |
---|
426 | |
---|
427 | # debug |
---|
428 | notify("created file '$htmlFile'"); |
---|
429 | |
---|
430 | # now move HTML file into the AFS target web folder |
---|
431 | |
---|
432 | } |
---|
433 | |
---|
434 | |
---|
435 | sub updateMadForWindowsSambaFolder{ |
---|
436 | my $localDir = `pwd`; |
---|
437 | chdir($madForWindowsSambaFolder); |
---|
438 | # ideally we should do a complete clean-up here. |
---|
439 | print "invoke CVS update in $madForWindowsSambaFolder. Ideally should do a complete clean-up before\n"; |
---|
440 | `cvs update`; |
---|
441 | $cvsStatus = `cvs status`; |
---|
442 | if ($debug eq 'yes'){ |
---|
443 | notify("outcome of `cvs update`: $cvsStatus"); |
---|
444 | } |
---|
445 | chdir ($localDir); # back to where we were before entering the sub |
---|
446 | |
---|
447 | } |
---|
448 | |
---|
449 | |
---|
450 | sub notify{ |
---|
451 | if ($debug eq 'yes') { |
---|
452 | my $message = $_[0]; |
---|
453 | my $msg = MIME::Lite->new( |
---|
454 | From => 'MAD-X Windows compilation robot', |
---|
455 | ReplyTo => 'Jean-Luc.Nougaret@cern.ch', |
---|
456 | To => 'Jean-Luc.Nougaret@cern.ch', |
---|
457 | Subject => 'automatic notification', |
---|
458 | Data => $message |
---|
459 | ); |
---|
460 | $msg->send; |
---|
461 | } # else do nothing |
---|
462 | |
---|
463 | } |
---|